【问题标题】:Extract email addresses from log with grep or sed使用 grep 或 sed 从日志中提取电子邮件地址
【发布时间】:2017-06-11 21:00:41
【问题描述】:
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))

从上面的邮件日志中,我想提取括在尖括号&lt; ... &gt; 之间的电子邮件地址,例如。 to=&lt;wanted1918_ke@yahoo.com&gt;wanted1918_ke@yahoo.com

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

【问题讨论】:

    标签: regex awk sed grep cut


    【解决方案1】:

    使用 GNU grep,只需使用包含向后看和向前看的正则表达式:

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

    这就是说:嘿,提取所有以to=&lt;&gt; 开头的字符串。

    【讨论】:

      【解决方案2】:

      你可以像这样使用awk

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

      我用to=&lt;&gt;, 分割行并打印第二个字段。

      【讨论】:

        【解决方案3】:
        awk -F'[<>]' '{print $2}' file
        
        wanted1918_ke@yahoo.com
        devi_joshi@yahoo.com
        

        【讨论】:

        • 这可能太弱了。将其锚定在 to=&lt; 是一个好主意。
        【解决方案4】:

        只是为了展示一个sed 替代方案(由于-E,需要GNU 或BSD/macOS sed):

        sed -E 's/.* to=<(.*)>.*/\1/' file
        

        注意正则表达式必须如何匹配整个行,以便捕获组匹配(电子邮件地址)的替换匹配。

        稍微更有效 - 但可能不太可读 - 变体是
        sed -E 's/.* to=&lt;([^&gt;]*).*/\1/' file


        由于 BRE 所需的遗留语法(基本正则表达式),符合 POSIX 的公式会稍微麻烦一些:

        sed 's/.* to=<\(.*\)>.*/\1/' file
        

        fedorqui's helpful GNU grep answer 的变体:

        grep -Po ' to=<\K[^>]*' file
        

        \K 会丢弃所有匹配到该点的所有内容,它不仅在语法上比后向断言 ((?&lt;=...)) 更简单,而且更灵活 - 它支持 变量 长度表达式- 并且更快(尽管在许多实际情况下这可能无关紧要;如果性能至关重要:见下文)。


        性能对比

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

        请注意,这在许多用例中可能无关紧要,但可以深入了解:

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

        绝对值并不重要,但相对性能有望提供一些见解。请参阅底部生成这些数字的脚本,这些数字是在 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
        

        【讨论】:

          猜你喜欢
          • 2023-03-03
          • 2016-04-12
          • 2011-12-17
          • 1970-01-01
          • 1970-01-01
          • 2011-08-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多