使用 grep 或 sed 从日志中提取电子邮件地址
Extract email addresses from log with grep or sed
Jan 23 00:46:24 portal postfix/smtp[31481]: 1B1653FEA1: to=<wanted1918_ke@yahoo.com>, relay=mta5.am0.yahoodns.net[98.138.112.35]:25, delay=5.4, delays=0.02/3.2/0.97/1.1, dsn=5.0.0, status=bounced (host mta5.am0.yahoodns.net[98.138.112.35] said: 554 delivery error: dd This user doesn't have a yahoo.com account (wanted1918_ke@yahoo.com) [0] - mta1321.mail.ne1.yahoo.com (in reply to end of DATA command))
Jan 23 00:46:24 portal postfix/smtp[31539]: AF40C3FE99: to=<devi_joshi@yahoo.com>, relay=mta7.am0.yahoodns.net[98.136.217.202]:25, delay=5.9, delays=0.01/3.1/0.99/1.8, dsn=5.0.0, status=bounced (host mta7.am0.yahoodns.net[98.136.217.202] said: 554 delivery error: dd This user doesn't have a yahoo.com account (devi_joshi@yahoo.com) [0] - mta1397.mail.gq1.yahoo.com (in reply to end of DATA command))
我想从上面的邮件日志中提取 angular 方括号 < ... >
中包含的电子邮件地址,例如。 to=<wanted1918_ke@yahoo.com>
至 wanted1918_ke@yahoo.com
我正在使用 cut -d' ' -f7
来提取电子邮件,但我很好奇是否有更灵活的方法。
使用 GNU grep,只需使用包含后视和前视的正则表达式:
$ grep -Po '(?<=to=<).*(?=>)' file
wanted1918_ke@yahoo.com
devi_joshi@yahoo.com
这是说:嘿,提取所有以 to=<
开头并后跟 >
的字符串。
您可以这样使用 awk
:
awk -F'to=<|>,' '{print }' the.log
我按 to=<
或 >,
拆分行并打印第二个字段。
awk -F'[<>]' '{print }' file
wanted1918_ke@yahoo.com
devi_joshi@yahoo.com
只是为了显示 sed
替代方案(由于 -E
,需要 GNU 或 BSD/macOS sed
):
sed -E 's/.* to=<(.*)>.*//' file
请注意正则表达式必须如何匹配 整个 行,以便 capture-group 匹配(电子邮件地址)的替换产生 only 匹配。
A 稍微 更有效 - 但可能不太可读 - 变化是
sed -E 's/.* to=<([^>]*).*//' file
由于 BRE 所需的遗留语法(基本 正则表达式),POSIX-compliant 公式有点麻烦:
sed 's/.* to=<\(.*\)>.*//' file
的变体:
grep -Po ' to=<\K[^>]*' file
\K
,它会丢弃匹配到该点的所有内容,不仅在语法上比 look-behind 断言更简单((?<=...)
,而且更灵活 - 它支持 变量-length 表达式 - 更快(尽管在许多 real-world 情况下这可能无关紧要;如果性能最重要:请参见下文)。
性能比较
以下是此页面上各种解决方案的性能比较。
请注意,这在许多用例中可能并不重要,但可以深入了解:
- 各种标准实用程序的相对性能
- 对于给定的实用程序,调整正则表达式会产生怎样的影响。
绝对值并不重要,但相对性能有望提供一些见解。请参阅生成这些数字的脚本的底部,这些数字是在 2012 年末的 27" iMac 运行 macOS 10.12.3 上获得的,使用通过复制问题中的示例输入创建的 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
结合使用
- 调整正则表达式以避免回溯和 look-behind 断言会有所作为。
- GNU
sed
出奇地慢,而 GNU awk
比 BSD awk
快。奇怪的是,GNU sed
. 的(部分)non-backtracking 解决方案 较慢
这是生成上述时间的脚本;请注意,g
前缀的命令是 GNU 实用程序,它们是通过 Homebrew 安装在 macOS 上的;同样,mawk
是通过 Homebrew 安装的。
请注意,"non-backtracking" 仅部分 适用于某些命令。
#!/usr/bin/env bash
# Define the test commands.
test01=( 'BSD sed' sed -E 's/.*to=<(.*)>.*//' )
test02=( 'BSD sed, non-backtracking' sed -E 's/.*to=<([^>]*).*//' )
# ---
test03=( 'GNU sed' gsed -E 's/.*to=<(.*)>.*//' )
test04=( 'GNU sed, non-backtracking' gsed -E 's/.*to=<([^>]*).*//' )
# ---
test05=( 'BSD awk' awk -F' to=<|>,' '{print }' )
test06=( 'GNU awk' gawk -F' to=<|>,' '{print }' )
test07=( 'Mawk' mawk -F' to=<|>,' '{print }' )
#--
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", /runs); print , avg "s" }
' | column -s$'\t' -t
Jan 23 00:46:24 portal postfix/smtp[31481]: 1B1653FEA1: to=<wanted1918_ke@yahoo.com>, relay=mta5.am0.yahoodns.net[98.138.112.35]:25, delay=5.4, delays=0.02/3.2/0.97/1.1, dsn=5.0.0, status=bounced (host mta5.am0.yahoodns.net[98.138.112.35] said: 554 delivery error: dd This user doesn't have a yahoo.com account (wanted1918_ke@yahoo.com) [0] - mta1321.mail.ne1.yahoo.com (in reply to end of DATA command))
Jan 23 00:46:24 portal postfix/smtp[31539]: AF40C3FE99: to=<devi_joshi@yahoo.com>, relay=mta7.am0.yahoodns.net[98.136.217.202]:25, delay=5.9, delays=0.01/3.1/0.99/1.8, dsn=5.0.0, status=bounced (host mta7.am0.yahoodns.net[98.136.217.202] said: 554 delivery error: dd This user doesn't have a yahoo.com account (devi_joshi@yahoo.com) [0] - mta1397.mail.gq1.yahoo.com (in reply to end of DATA command))
我想从上面的邮件日志中提取 angular 方括号 < ... >
中包含的电子邮件地址,例如。 to=<wanted1918_ke@yahoo.com>
至 wanted1918_ke@yahoo.com
我正在使用 cut -d' ' -f7
来提取电子邮件,但我很好奇是否有更灵活的方法。
使用 GNU grep,只需使用包含后视和前视的正则表达式:
$ grep -Po '(?<=to=<).*(?=>)' file
wanted1918_ke@yahoo.com
devi_joshi@yahoo.com
这是说:嘿,提取所有以 to=<
开头并后跟 >
的字符串。
您可以这样使用 awk
:
awk -F'to=<|>,' '{print }' the.log
我按 to=<
或 >,
拆分行并打印第二个字段。
awk -F'[<>]' '{print }' file
wanted1918_ke@yahoo.com
devi_joshi@yahoo.com
只是为了显示 sed
替代方案(由于 -E
,需要 GNU 或 BSD/macOS sed
):
sed -E 's/.* to=<(.*)>.*//' file
请注意正则表达式必须如何匹配 整个 行,以便 capture-group 匹配(电子邮件地址)的替换产生 only 匹配。
A 稍微 更有效 - 但可能不太可读 - 变化是
sed -E 's/.* to=<([^>]*).*//' file
由于 BRE 所需的遗留语法(基本 正则表达式),POSIX-compliant 公式有点麻烦:
sed 's/.* to=<\(.*\)>.*//' file
grep -Po ' to=<\K[^>]*' file
\K
,它会丢弃匹配到该点的所有内容,不仅在语法上比 look-behind 断言更简单((?<=...)
,而且更灵活 - 它支持 变量-length 表达式 - 更快(尽管在许多 real-world 情况下这可能无关紧要;如果性能最重要:请参见下文)。
性能比较
以下是此页面上各种解决方案的性能比较。
请注意,这在许多用例中可能并不重要,但可以深入了解:
- 各种标准实用程序的相对性能
- 对于给定的实用程序,调整正则表达式会产生怎样的影响。
绝对值并不重要,但相对性能有望提供一些见解。请参阅生成这些数字的脚本的底部,这些数字是在 2012 年末的 27" iMac 运行 macOS 10.12.3 上获得的,使用通过复制问题中的示例输入创建的 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
结合使用
- 调整正则表达式以避免回溯和 look-behind 断言会有所作为。
- GNU
sed
出奇地慢,而 GNUawk
比 BSDawk
快。奇怪的是,GNUsed
. 的(部分)non-backtracking 解决方案 较慢
这是生成上述时间的脚本;请注意,g
前缀的命令是 GNU 实用程序,它们是通过 Homebrew 安装在 macOS 上的;同样,mawk
是通过 Homebrew 安装的。
请注意,"non-backtracking" 仅部分 适用于某些命令。
#!/usr/bin/env bash
# Define the test commands.
test01=( 'BSD sed' sed -E 's/.*to=<(.*)>.*//' )
test02=( 'BSD sed, non-backtracking' sed -E 's/.*to=<([^>]*).*//' )
# ---
test03=( 'GNU sed' gsed -E 's/.*to=<(.*)>.*//' )
test04=( 'GNU sed, non-backtracking' gsed -E 's/.*to=<([^>]*).*//' )
# ---
test05=( 'BSD awk' awk -F' to=<|>,' '{print }' )
test06=( 'GNU awk' gawk -F' to=<|>,' '{print }' )
test07=( 'Mawk' mawk -F' to=<|>,' '{print }' )
#--
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", /runs); print , avg "s" }
' | column -s$'\t' -t