将行的第一部分与固定字符串匹配并计算第二部分中的唯一值

Match the first part of lines against a fixed string and count unique values in the second part

$var 包含数千行,格式如下:

./abc bbd xyh doc
./docs 2019 abc docx
./docs 2019 abc docx
./docs 2019 abc ppt
./docs 2019 abc ppt
./docs 2019 abc xls
./docs 2019 abc def docx
./docs 2019 abc/def docx
./bdg/aabc/dd efgh 2018 doc
. xls
. pptx
./aax bcd/def/gfhe ttp/five ppt

最后一列代表文件的 extension,其他所有内容(从每行的开头,直到最后一个空白字符)是相应文件的 basename(路径)。

有一个 while 循环为 $path 生成值,其中包含测试 basename(路径),我的目标是从 $var 中删除所有不匹配的行 $path 从行首开始,直到最后一个空格(不包括最后一列)。此外,我只想打印相应的扩展名(如 | sort | uniq -c)。

例如,如果在 while 循环的迭代期间我们发送 path="./docs 2019 abc",输出应该是实现以下目标的最快方式

2 docx
2 ppt
1 xls

这就是我最终得到的结果,但输出是 错误的 - 它打印基本名称,而不是扩展名,并且每次迭代都非常慢:

printf "echo -e \"%s\" | awk '{$NF=\"\";} ( $0 ~ /%s/ )' | sort | uniq -c | sort -k1 -nr" "${var}" "${path//\//\/}" | bash

输出:

2 ./docs 2019 abc
2 ./docs 2019 abc
1 ./docs 2019 abc
$ path='./docs 2019 abc'
$ grep -Pox "\Q$path\E\s\K\S+" <<< ${var} | sort | uniq -c
      2 docx
      2 ppt
      1 xls

这使用了 PCRE and thus requires GNU grep.


对于 GNU awk 它将是:

$ cat prog.awk
gensub(/\s\S+$/, "", 1) == path {
  cnt[$NF]++
}
END {
  PROCINFO["sorted_in"] = "@val_num_desc"
  for (ext in cnt) {
    print cnt[ext], ext
  }
}
$ gawk -v path='./docs 2019 abc' -f prog.awk <<< ${var}
2 docx
2 ppt
1 xls

这种方法可能比前者更快,因为它不会生成 sortuniq


以防万一 none 上述工具可用,这里有一个可移植的解决方案:

$ cat prog.awk
{
  ext = $NF
  sub(/[[:space:]][^[:space:]]+$/, "")
  if ([=13=] == path)
    cnt[ext]++
}
END {
  for (ext in cnt)
    print cnt[ext], ext
}
$ awk -v path='./docs 2019 abc' -f prog.awk <<< ${var} | sort -k1nr
2 docx
2 ppt
1 xls

请注意,所有这些都严重依赖于您对输入的描述,并且不会处理您可能错过的任何边缘情况。