grep 具有不同数量的匹配组的多个模式?

grep 具有不同数量的匹配组的多个模式?

我正在尝试统计 git diff 的插入和删除。

我有以下内容,当通过管道传输一个或多个形式的字符串时"4 files changed, 629607 insertions(+), 123 deletions(-)"可以计算总数:

grep -Eo 'changed, ?(\d+) insertion.*(\d+) deletion' | awk '{ i+=$2; d+=$4 } END { print "insertions: ",i," deletions: ",d }'

这会产生insertions: 629607 deletions: 123

然而,有时 diff 并不遵循上述格式,而是只有插入或只有删除。

在这些情况下,我不需要匹配两个数字,而只需要匹配一个(并确保它最终出现在右列中)。

如何创建一个足够灵活的正则表达式来处理这些变化,并且生成awk可以正确计数的输出?

答案1

我会Perl在这里使用灵活性、可读性和可移植性,没有复杂的正则表达式,让我们KISS(...我只使用一根管子git)。

它在任何情况下都有效:有或没有模式之一,否则这将跳过根本不匹配的行:

$ git diff
7 insertions, 1 deletions
1 deletions
3 insertions
foobar
$ git diff | perl -nE '
    BEGIN{our $insert = our $delete = 0}
    $insert += $1 if /(\d+)\s+insertion/;
    $delete += $1 if /(\d+)\s+deletion/;
    END{say $insert . " insertions, " . $delete . " deletions"}
'
10 insertions, 2 deletions

答案2

其他发帖者已经回答了如何直接解决您的问题。但是,既然您提到您正在解析 的结果git diff,我建议采用稍微不同的方法。

diff如果你想在脚本中使用输出,你可以使用--numstat代替--stat

您最终将得到一致的输出,因为其目的--numstat是用于脚本。

使用时git diff --stat,您会得到以下输出:

$ git diff main --stat
 [...list of files...]
 5 files changed, 112 insertions(+), 20 deletions(-)

使用时git diff --statnum,您会得到以下输出:

$ git diff main --statnum
-       -       some/binary/file
15      0       some/file
1       1       some/other/file
29      7       another/file
67      12      yet/another/file

上面的结构只是一个三列结构。第一列是插入次数,第二列是删除次数,最后一列是文件名。

您可以通过管道传输命令来汇总列,awk而不必担心是否有任何插入和/或删除。

$ git diff main --numstat | awk '{sum_insertions+=$1;sum_deletions+=$2}END{print "insertions:", sum_insertions+0, "deletions:", sum_deletions+0;}'
insertions: 112 deletions: 20

更多链接:git-diff 手册页 / 其他 diff 格式

答案3

grep并没有真正提供捕获组的良好视图,所以我在这里切换到 Perl。通过此测试输入foo.txt

2 files changed, 2 insertions(+), 7 deletions(-)
1 file changed, 9 deletions(-)
garbage
1 file changed, 10 insertions(+)

你可以这样做:

$ perl -ne '/files? changed, (?:(\d+) insertion\S*)? ?(?:(\d+) deletion)?/ && printf "%d %d\n", $1, $2' < foo.txt
2 7
0 9
10 0

或者也用 Perl 进行求和:

$ perl -ne 'if (/files? changed, (?:(\d+) insertion\S*)? ?(?:(\d+) deletion)?/) { $i += $1; $d += $2 } END { printf "insertions: %d deletions: %d\n", $i, $d }' < foo.txt
insertions: 12 deletions: 16

这里的要点是,用于插入的组在?其后面是可选的,并且捕获组的编号从左到右,无论它们是否匹配。再加上一些捏造,以便所有三种可能的输入格式都匹配。当然,您也可以针对/, (\d+) insertion/和进行两场单独的比赛/, (\d+) deletion/,等等。

答案4

GNU awk 解决方案与 Perl 基本相同

gawk -F'\n' '
  match($0, /([0-9]+)\s+insertion/, i) { total_i += i[1]; }
  match($0, /([0-9]+)\s+deletion/, d) { total_d += d[1]; }
  END {
    printf("insertions: %d  deletions: %d\n", total_i, total_d);
  }
'

POSIX awk 没有捕获组,但为了避免任何额外的split()步骤,可以依靠 awk 使用匹配字符串的初始数字部分,并在数字计算中自动删除其余部分,即添加“3 个插入”会导致添加“3 ”。

awk -F'\n' '
  match($0, /[[:digit:]]+[[:space:]]+insertion/) {
    total_i += substr($0, RSTART, RLENGTH)
  }
  match($0, /[[:digit:]]+[[:space:]]+deletion/) {
    total_d += substr($0, RSTART, RLENGTH)
  }
  END {
    printf "insertions: %d  deletions: %d\n", total_i, total_d
  }
'

如果你有一个 POSIX 之前的 awk,那么就买一个新的,但如果由于某种原因这是不可能的,那么更改[[:digit:]][0-9][[:space:]]to [ \t],然后这将在任何 awk 中工作(当然,旧的损坏的 awk 除外)。

相关内容