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

时间:2017-04-05 14:33:46

标签: bash awk grep

我正在尝试使用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 个答案:

答案 0 :(得分: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。它通常不会针对生产代码进行推荐,但对于oneliners来说是可以接受的。

演示:

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 :(得分:1)

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

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

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

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

答案 2 :(得分:1)

单输入案例

$ 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 ' ' -用空格替换换行符,以获得单行,空格分隔的数字列表。

    • 操作数-表示stdin,在paste的BSD / macOS版本中是必需的(GNU paste可选)。
    • -s按顺序连接输入行。
    • d' '指定空格char。连接时应该用作输入行之间的分隔符(分隔符); paste的默认值是tab char。 (\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' <(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+)?'

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

答案 3 :(得分: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.out file2.out file3.out 。< / p>

输出:

100  1000  3212.97
100  1000  3212.97
100  1000  3212.97

编辑(回应mklement0的评论):

要仅处理包含单词“吞吐量”的所有行,下面是一个稍微扩展的示例:

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

答案 4 :(得分:1)

所有这些解决方案似乎都很复杂。呈现的一个不是特别有效,但是可以工作:

 - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: drop'
      inputs:
        pathtoPublish: '$(Build.ArtifactStagingDirectory)\\package\\' 
        artifactName: 'strategy' 

它的作用:

1)分别从文件while read -r line do echo $line | grep -o "PATTERN" | tr "\n" " " ; echo done < grep.txt 中读取每一行,并摸索模式。这可以让您拥有多种模式,而不受任何特定数字或非常特定的正则表达式的约束

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

3)最后,tr命令建立以移至下一行

最终得到的是完全按照要求来自同一行中echo中同一行的模式。

答案 5 :(得分: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 

答案 6 :(得分:0)

这是一个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

答案 7 :(得分:0)

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

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

100 1000 3212.97

希望这有帮助!

答案 8 :(得分: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'