使用grep或sed从日志中提取电子邮件地址

时间:2017-01-26 11:38:17

标签: regex awk sed grep cut

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))

从上面的maillog我想提取括号< ... >之间的电子邮件地址,例如。 to=<wanted1918_ke@yahoo.com>wanted1918_ke@yahoo.com

我正在使用cut -d' ' -f7来提取电子邮件,但我很好奇是否有更灵活的方法。

4 个答案:

答案 0 :(得分:5)

使用GNU grep,只需使用包含外观的正则表达式并向前看:

$ grep -Po '(?<=to=<).*(?=>)' file
wanted1918_ke@yahoo.com
devi_joshi@yahoo.com

这说:嘿,提取所有字符串前面加to=<后跟>

答案 1 :(得分:3)

您可以像这样使用awk

awk -F'to=<|>,' '{print $2}' the.log

我按to=<>,拆分行并打印第二个字段。

答案 2 :(得分:3)

仅显示sed替代方案(由于sed需要GNU或BSD / macOS -E):

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,删除与此相匹配的所有内容,不仅在语法上比后面的断言((?<=...)更简单,而且更灵活 - 它支持变量 -length表达式 - 并且更快(尽管在许多实际情况下这可能无关紧要;如果性能至关重要:见下文)。

性能比较

以下是本页各种解决方案在性能方面的比较。

请注意,在许多用例中这可能并不重要,但可以深入了解:

  • 各种标准实用程序的相对性能
  • 对于给定的实用程序,如何调整正则表达式可以产生影响。

绝对值并不重要,但相对表现有望提供一些见解。请参阅底部,了解产生这些数字的脚本,这些数据是在2012年末27和34岁时获得的。 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
  • 结合使用
  • 调整正则表达式以避免回溯和后视断言可能会产生影响。
  • GNU sed出乎意料地慢,而GNU awk比BSD awk快。奇怪的是,(部分)非回溯解决方案较慢与GNU sed

这是产生上述时间的脚本;请注意,g - 前缀命令是通过Homebrew安装在macOS上的 GNU 实用程序;同样,mawk是通过Homebrew安装的。

请注意&#34;非回溯&#34;仅将部分应用于某些命令。

#!/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

答案 3 :(得分:0)

awk -F'[<>]' '{print $2}' file

wanted1918_ke@yahoo.com
devi_joshi@yahoo.com