【问题标题】:Print multiple regex matches using grep on the same line在同一行上使用 grep 打印多个正则表达式匹配
【发布时间】:2017-04-05 14:33:45
【问题描述】:

我正在尝试使用 grep 匹配所有数字,包括整数和小数,并在同一行打印匹配项(以便使用 gnuplot 进行绘图)。例如,

echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | grep -E -o '\d+(\.\d+)?'

打印

100
1000
3212.97

但是我如何在同一行中获得所有这些内容,如下所示?

100  1000  3212.97

编者注:问题的原始形式仅使用 \d+ 作为正则表达式,这反映在一些较旧的答案中。

最终,我希望它可以处理多个输入文件,例如:

grep Throughput *.out | grep -E -o '\d+(\.\d+)?'

应该打印

100  1000  3212.97
200  3000  5444.77
300  5000  6769.32

【问题讨论】:

    标签: bash awk grep


    【解决方案1】:

    所有这些解决方案似乎都非常复杂。提出的那个不是特别有效但有效:

    while read -r line
    do
    echo $line | grep -o "PATTERN"  | tr "\n" " "  ; echo 
    done < grep.txt
    

    它的作用:

    1) 分别从文件grep.txt 中读取每一行并用greps 查找模式。这允许您拥有多种模式,您不受任何特定数字或非常具体的正则表达式的限制

    2) 然后用tr 删除所有不必要的换行符,将它们转换为空格(对于具有任意数量模式的每个特定行,而不是整个文件)

    3) 最后echo 命令建立移动到下一行

    你最终得到的是来自同一行grep.txt同一行的模式,完全符合要求。

    【讨论】:

    • 这个理解起来非常好。
    【解决方案2】:

    其他一些变体:

    下面的每个示例都使用这个正则表达式:

    (\d+\.\d*|\.\d+|\d+)
    

    它匹配,(在一组中)ddd.ddd.ddd.dddddd。如果您的小数不同,例如不想捕获 .ddd(仅小数)变体,只需将其从正则表达式中删除即可。

    用于一个文件/字符串

    #using `paste`
    echo "bench-100-net-buffering1000.out:Throughput: 3212.97"  | grep -Eo '(\d+\.\d*|\.\d+|\d+)' | paste -s -
    # using echo for making the "one line"
    echo $(grep -Eo '(\d+\.\d*|\.\d+|\d+)' <<< "bench-100-net-buffering1000.out:Throughput: 3212.97")
    #HERESTRING and different separator
    grep -Eo '(\d+\.\d*|\.\d+|\d+)' <<< "bench-100-net-buffering1000.out:Throughput: 3212.97" | paste -sd, -
    #process substitution.. ;)
    paste -sd ' ' <(grep -Eo '(\d+\.\d*|\.\d+|\d+)' <<< "bench-100-net-buffering1000.out:Throughput: 3212.97")
    

    多个文件同上,使用bash 循环。在示例中使用 ff* 作为文件名。

    #Using null-term find
    while IFS= read -r -d '' file; do
            grep -Eo '(\d+\.\d*|\.\d+|\d+)' "$file" | paste -s -
    done < <(find . -maxdepth 1 -type f -name ff\* -print0)
    
    # or alternative - also prints filenames
    while IFS= read -r -d '' file; do
            echo "$file:" $(grep -Eo '(\d+\.\d*|\.\d+|\d+)' $file)
    done < <(find . -maxdepth 1 -type f -name ff\* -print0)
    
    echo Using FOR loop
    for file in ff* ; do
            grep -Eo '(\d+\.\d*|\.\d+|\d+)' "$file" | paste -s -
    done
    

    perl 变体:

    perl -0777 -nE 'say "@{[/(\d+\.\d*|\.\d+|\d+)/g]}"' ff*
    

    也打印文件名

    perl -0777 -nE 'say "$ARGV @{[/(\d+\.\d*|\.\d+|\d+)/g]}"' ff*
    

    也可以使用不同的字段分隔符\t

    perl -0777 -nE '$"="\t";say "$ARGV @{[/(\d+\.\d*|\.\d+|\d+)/g]}"' ff*
    

    所有perl 解决方案都使用baby-cart operator。通常不建议将其用于生产代码,但可以用于单行代码。

    演示:

    perl -0777 -nE 'say "@{[/(\d+\.\d*|\.\d+|\d+)/g]}"' <<< "some-111-decimal-222.-another-333.33-only-frac-.444.txt"
    

    输出

    111 222. 333.33 .444
    

    【讨论】:

    • 关于你的 perl 答案,我很确定他们只想要 Throughput 行。
    • @123 他说:正在尝试匹配所有数字 ...所以,全部 :)
    • 我的有小数点?
    • 他们的多文件示例是grep Throughput *.out | grep -E '\d+(\.\d+)?',这表明他们只想要Throughput 行。
    • 我不明白这是什么意思?
    【解决方案3】:

    这里有一个 gnu awk 命令来获取您的输出:

    echo "bench-100-net-buffering1000.out:Throughput: 3212.97" |
    awk 'n = split($0, a, /[0-9]*\.?[0-9]+/, vals) {
       for (i=1; i<=n; i++)
          printf "%s%s", vals[i], (i == n ? ORS : OFS)
    }'
    
    100 1000 3212.97
    

    【讨论】:

      【解决方案4】:

      我喜欢 Perl 中的这个解决方案 - 这也应该能正确获取浮点数:

      perl -ne 'print join("\t", /(\d+(?:.\d+))/g); print "\n"' files*

      join 的第一个参数给出了字段分隔符

      ?: 创建了一个所谓的非捕获组,以避免在输出中复制浮点之后的部分 - 请参阅:https://perldoc.perl.org/perlretut.html#Non-capturing-groupings

      【讨论】:

        【解决方案5】:

        对于您的第一个简单案例,您可以通过以下方式获得所需的输出:

        echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | 
        grep -o -E '[0-9]*\.?[0-9]+' | column
        

        输出:

        100  1000  3212.97
        

        编辑:

        感谢 mklement0,他指出使用 paste 而不是 column 可能是更好的解决方案:

        echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | 
        grep -o -E '[0-9]*\.?[0-9]+' | paste -s -
        

        对于多个输入文件,我也更喜欢 perl 解决方案,因为它似乎相当简单明了:

        perl -nE 'say join "\t", /[0-9]*\.?[0-9]+/g' *.out
        

        本示例使用(仅用于演示)三个相同的输入文件 file1.outfile2.outfile3.out

        输出:

        100  1000  3212.97
        100  1000  3212.97
        100  1000  3212.97
        

        编辑(回应 mklement0 的评论):

        为了只处理所有包含单词“Throughput”的行,这里有一个稍微扩展的例子:

        perl -nE 'say join "\t", /[0-9]*\.?[0-9]+/g if /Throughput/' *.out
        

        【讨论】:

          【解决方案6】:

          单输入大小写:

          $ echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | 
              grep -E -o '[0-9]+(\.[0-9]+)?' |
                paste -sd' ' -
          100 1000 3212.97
          
          • 请注意,鉴于您未指定平台,我已通过将 \d 替换为 [0-9] 将正则表达式更改为符合 POSIX 标准。

            • BSD/macOS grep 总是能理解 \d,但 GNU grep 只能通过 -P 选项来理解,而 BSD/macOS 不支持。
          • paste -sd ' ' - 用空格替换换行符以获得单行、空格分隔的数字列表。

            • 操作数- 表示标准输入,在paste 的BSD/macOS 版本中是必需的(对于GNU paste 是可选的)。
            • -s 按顺序连接输入行。
            • d' ' 指定一个空格字符。连接时应用作输入行之间的分隔符(分隔符)paste 的默认值是制表符。 (\t)。
            • 以这种方式使用paste 优于tr '\n' ' ',因为后者会产生尾随空格。
              paste 也优于column,因为后者会在输出行变宽时插入换行符比显示(并且总是使用\t作为分隔符(-s选项仅适用于-t,此处不能使用)。
              也就是说,paste 不能使用 多字符 字符串作为固定分隔符;问题中的示例输出当前使用 2 空格作为分隔符字符串,因此,如果您想实现这一点,请将paste 的输出通过管道传输到sed 's/ / /g

          多文件输入案例

          下面的解决方案使用一个 shell 循环和 2 个 grep 调用和一个 paste 调用每个输入文件;考虑改用更简洁高效的Perl solution from inferno's helpful answer

          如果您愿意假设所有匹配的行正好包含 3 个数字,则可以使用greppaste 的更有效解决方案(改编自解决方案尝试OP本人); paste 用于应用传递给 -d 的 3 个分隔符(空格、空格、换行符)单独、循环
          paste -sd ' \n' &lt;(grep -h Throughput *.out | grep -Eo '[0-9]+(\.[0-9]+)?')

          对于文件特定的输出,您必须单独处理文件(这假设给定文件中匹配行的所有数字应该是输出为单行):

          for file in *.out; do
            grep Throughput "$file" | grep -Eo '[0-9]+(\.[0-9]+)?' | paste -sd ' ' -
          done
          
          • for file in *.out 分别循环遍历所有匹配的文件。

          • grep Throughput "$file" 输出手头文件中包含Throughput 的所有行。

          • | grep -Eo '[0-9]+(\.[0-9]+)?' 然后从这些行中提取数字,并将每个数字打印在自己的行上。

          • | paste -sd ' ' - 然后用空格替换换行符以获得每个文件的单行数字列表。


          至于为什么你的方法行不通

          grep Throughput *.out | grep -Eo '\d+(\.\d+)?'
          

          通过管道发送单个匹配行的流所有输入文件,因此后续命令无法知道哪些行来自哪个文件或行,使得不可能对每个输入文件或行的数字进行分组(在后续步骤中) - 除非您可以假设每个输入行中包含的确切、固定数量的数字。

          【讨论】:

            【解决方案7】:

            为什么不使用 sed?简单丑陋的解决方案(欢迎反馈):

            $ echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | sed -re 's/[^0-9]+/ /g;s/ +/ /g;s/^ //' 
            100 1000 3212 97
            

            或显式匹配整数和浮点数:

            $ echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | sed -re 's/([^0-9]+)([0-9]+|[0-9]+\.[0-9]+)/\2 /g'
            100 1000 3212.97 
            

            【讨论】:

            • 如果 sed 支持 -r,你几乎可以肯定使用 ; 而不是单独的 -e's
            • 感谢@123。为什么; 比几个-e 参数更好?
            • 我想这不是技术上的,我只是觉得它更具可读性。
            • 使用-E,而不是-r,以便移植到其他seds。 -r 仅适用于 GNU,而 -E 适用于 GNU 和 OSX。
            【解决方案8】:

            根据您的问题,这是一个简单的命令,可以获取您想要获得的输出。

            echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | grep -oE '[0-9]+(\.[0-9]+)?' | tr '\n' ' ' |  paste -s
            
            100 1000 3212.97
            

            希望这会有所帮助!

            【讨论】:

            • 如果你使用tr '\n' ' '(这不是一个好主意,因为它会添加一个尾随空格),paste -s 完全没有效果。单个paste 命令应该可以。
            【解决方案9】:

            我真的很喜欢 anubhava awk 脚本。

            我希望通过更多gnu awk 功能对其进行改进,使其更简单、更简洁。

            这个技巧将打印输入行中的所有数字,不管有多少。

            echo "bench-100-net-buffering1000.out:Throughput: 3212.97" |
            awk 'BEGIN {FPAT="[0-9]*\\.?[0-9]+"} {  # define input fields to be numbers
                $1 = $1; # recalculate the input line to hold only input fields
                print;   # print recalculated input line
            }'
            

            或者用一个衬垫:

            echo "bench-100-net-buffering1000.out:Throughput: 3212.97" |
            awk 'BEGIN{FPAT="[0-9]*\\.?[0-9]+"}{$1=$1}1'
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-02-25
              • 1970-01-01
              • 2017-04-13
              • 1970-01-01
              • 2015-10-21
              • 1970-01-01
              • 2020-10-06
              • 2021-12-06
              相关资源
              最近更新 更多