只是为了展示一个sed 替代方案(由于-E,需要GNU 或BSD/macOS sed):
sed -E 's/.* to=<(.*)>.*/\1/' file
注意正则表达式必须如何匹配整个行,以便捕获组匹配(电子邮件地址)的替换仅匹配。
稍微更有效 - 但可能不太可读 - 变体是
sed -E 's/.* to=<([^>]*).*/\1/' file
由于 BRE 所需的遗留语法(基本正则表达式),符合 POSIX 的公式会稍微麻烦一些:
sed 's/.* to=<\(.*\)>.*/\1/' file
fedorqui's helpful GNU grep answer 的变体:
grep -Po ' to=<\K[^>]*' file
\K 会丢弃所有匹配到该点的所有内容,它不仅在语法上比后向断言 ((?<=...)) 更简单,而且更灵活 - 它支持 变量 长度表达式- 并且更快(尽管在许多实际情况下这可能无关紧要;如果性能至关重要:见下文)。
性能对比
以下是此页面上各种解决方案在性能方面的比较。
请注意,这在许多用例中可能无关紧要,但可以深入了解:
- 各种标准实用程序的相对性能
- 对于给定的实用程序,调整正则表达式会产生怎样的影响。
绝对值并不重要,但相对性能有望提供一些见解。请参阅底部生成这些数字的脚本,这些数字是在 2012 年末运行 macOS 10.12.3 的 27" iMac 上获得的,使用通过复制问题中的示例输入创建的 250,000 行输入文件,平均 10运行每个。
Mawk 0.364s
GNU grep, \K, non-backtracking 0.392s
GNU awk 0.830s
GNU grep, \K 0.937s
GNU grep, (?>=...) 1.639s
BSD grep + cut 2.733s
GNU grep + cut 3.697s
BSD awk 3.785s
BSD sed, non-backtracking 7.825s
BSD sed 8.414s
GNU sed 16.738s
GNU sed, non-backtracking 17.387s
几个结论:
- 给定实用程序的具体实现很重要。
-
grep 一般是不错的选择,即使需要与cut 结合使用
- 调整正则表达式以避免回溯和后视断言可能会有所作为。
- GNU
sed 出奇地慢,而 GNU awk 比 BSD awk 快。奇怪的是,使用 GNU sed 的(部分)非回溯解决方案更慢。
这是产生上述时间的脚本;注意g-prefixed 命令是 GNU 实用程序,通过Homebrew 安装在 macOS 上;同样,mawk 是通过 Homebrew 安装的。
请注意,“非回溯”仅部分应用于某些命令。
#!/usr/bin/env bash
# Define the test commands.
test01=( 'BSD sed' sed -E 's/.*to=<(.*)>.*/\1/' )
test02=( 'BSD sed, non-backtracking' sed -E 's/.*to=<([^>]*).*/\1/' )
# ---
test03=( 'GNU sed' gsed -E 's/.*to=<(.*)>.*/\1/' )
test04=( 'GNU sed, non-backtracking' gsed -E 's/.*to=<([^>]*).*/\1/' )
# ---
test05=( 'BSD awk' awk -F' to=<|>,' '{print $2}' )
test06=( 'GNU awk' gawk -F' to=<|>,' '{print $2}' )
test07=( 'Mawk' mawk -F' to=<|>,' '{print $2}' )
#--
test08=( 'GNU grep, (?>=...)' ggrep -Po '(?<= to=<).*(?=>)' )
test09=( 'GNU grep, \K' ggrep -Po ' to=<\K.*(?=>)' )
test10=( 'GNU grep, \K, non-backtracking' ggrep -Po ' to=<\K[^>]*' )
# --
test11=( 'BSD grep + cut' "{ grep -o ' to=<[^>]*' | cut -d'<' -f2; }" )
test12=( 'GNU grep + cut' "{ ggrep -o ' to=<[^>]*' | gcut -d'<' -f2; }" )
# Determine input and output files.
inFile='file'
# NOTE: Do NOT use /dev/null, because GNU grep apparently takes a shortcut
# when it detects stdout going nowhere, which distorts the timings.
# Use dev/tty if you want to see stdout in the terminal (will print
# as a single block across all tests before the results are reported).
outFile="/tmp/out.$$"
# outFile='/dev/tty'
# Make `time` only report the overall elapsed time.
TIMEFORMAT='%6R'
# How many runs per test whose timings to average.
runs=10
# Read the input file up to even the playing field, so that the first command
# doesn't take the hit of being the first to load the file from disk.
echo "Warming up the cache..."
cat "$inFile" >/dev/null
# Run the tests.
echo "Running $(awk '{print NF}' <<<"${!test*}") test(s), averaging the timings of $runs run(s) each; this may take a while..."
{
for n in ${!test*}; do
arrRef="$n[@]"
test=( "${!arrRef}" )
# Print test description.
printf '%s\t' "${test[0]}"
# Execute test command.
if (( ${#test[@]} == 2 )); then # single-token command? assume `eval` must be used.
time for (( n = 0; n < runs; n++ )); do eval "${test[@]: 1}" < "$inFile" >"$outFile"; done
else # multiple command tokens? assume that they form a simple command that can be invoked directly.
time for (( n = 0; n < runs; n++ )); do "${test[@]: 1}" "$inFile" >"$outFile"; done
fi
done
} 2>&1 |
sort -t$'\t' -k2,2n |
awk -v runs="$runs" '
BEGIN{FS=OFS="\t"} { avg = sprintf("%.3f", $2/runs); print $1, avg "s" }
' | column -s$'\t' -t