在同一行上使用grep打印多个正则表达式的匹配结果

3

我想使用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
9个回答

2
所有这些解决方案似乎都过于复杂。所提供的解决方案并不是特别高效,但可行:
while read -r line
do
echo $line | grep -o "PATTERN"  | tr "\n" " "  ; echo 
done < grep.txt

它的作用:

1)单独读取grep.txt文件中的每一行,并使用grep查找模式。这使您可以拥有多个模式,不受任何特定数量或非常特定的正则表达式的限制。

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

3)最后,echo命令用于建立到下一行的移动。

最终结果是来自grep.txt相同行的模式在同一行上,完全符合要求。


这样理解起来会好得多。 - my chalupa

1
我喜欢Perl中的这个解决方案 - 这应该可以正确地获取浮点数: perl -ne 'print join("\t", /(\d+(?:.\d+))/g); print "\n"' files* join 的第一个参数给出了字段分隔符。 ?: 创建所谓的非捕获组,以避免在输出中重复浮点后面的部分 - 请参见: https://perldoc.perl.org/perlretut.html#Non-capturing-groupings

1

以下是一些其他变体:

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

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

它匹配(在一个组中)ddd. ddd.ddd .ddd ddd。如果您的小数不同,例如不想捕获.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

1
关于您的Perl答案,我非常确定他们只想要“吞吐量”行。 - 123
他们的多文件示例是 grep Throughput *.out | grep -E '\d+(\.\d+)?',这表明他们只想要 Throughput 行。 - 123
@jm666 我修改了你的答案,以获得我想要的精确输出 paste -sd ' '' ''\n' <(grep -Po '\d+(\.\d+)?' <<< \grep Throughput *.out`)但是一些系统(如较新的OS X版本)不支持grep中的-P` 标志(因为它不是gnu grep)。 - 0x0
@mklement0?不明白。为什么只有3个数字? - clt60
@mklement0 哦,好的! :) 我错过了 paste 的参数... 对不起打扰你了 :) - clt60
显示剩余6条评论

1
这里有一个单一的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

1
单输入情况:
$ echo "bench-100-net-buffering1000.out:Throughput: 3212.97" | 
    grep -E -o '[0-9]+(\.[0-9]+)?' |
      paste -sd' ' -
100 1000 3212.97

请注意,我已将正则表达式更改为符合POSIX标准,将\d替换为[0-9],因为您没有指定平台。 BSD/macOS的grep总是理解\d,但GNU的grep只有在使用-P选项时才能理解\d,而这个选项BSD/macOS不支持。 paste -sd ' ' -将换行符替换为空格,以获得一个单行、以空格分隔的数字列表。
  • 操作数-表示stdin,并且在BSD/macOS版本的paste中是必需的(与GNU的paste可选项相反)。
  • -s按顺序连接输入行。
  • d' '指定在连接输入行时应使用空格字符作为分隔符(分隔符)betweenpaste的默认值是制表符(\t)。
  • 使用这种方式的paste优于tr '\n' ' ',因为后者会产生尾随空格。
    paste也比column更可取,因为后者会在输出行变宽超过显示时插入换行符(并且总是使用\t作为分隔符(-s选项仅适用于-t,不能在此处使用)。
    也就是说,paste不能使用多字符字符串作为固定分隔符;问题中的示例输出目前使用2个空格作为分隔符字符串,因此如果要实现这一点,请将paste的输出导入到sed 's/ / /g中。

    多文件输入情况:

    以下解决方案使用了一个shell循环和每个输入文件2个grep调用和一个paste调用; 考虑使用更简洁高效的来自inferno有用答案的Perl解决方案

    如果你愿意假设所有匹配行都包含恰好3个数字,则可以使用更高效的greppaste解决方案(改编自OP本人的解决方案尝试); paste用于分别、循环地应用传递给-d(空格、空格、换行符)的3个分隔符字符:
    paste -sd ' \n' <(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+)?'
    

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

  • 1
    对于您的第一个简单案例,您可以通过以下方式获得所需的输出:
    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。 < p > 输出:< / p >
    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
    

    0

    我真的很喜欢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'
    

    0

    为什么不使用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 - 123
    1
    感谢 @123。为什么使用 ; 比多个 -e 参数更好? - Aif
    我猜这并不是技术上的问题,我只是觉得这样更易读。 - 123
    1
    使用-E而不是-r具有可移植性,适用于其他的sed。 -r仅适用于GNU版本,而-E适用于GNU和OSX。 - Ed Morton

    0
    根据您的问题,这里有一个简单的命令可以获得您想要的输出。
    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 命令就可以了。 - mklement0

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接