【问题标题】:Match the first part of lines against a fixed string and count unique values in the second part将行的第一部分与固定字符串匹配并计算第二部分中的唯一值
【发布时间】:2020-10-14 06:10:21
【问题描述】:

$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

【问题讨论】:

  • $var contains thousands of lines 对任何问题来说都是一个糟糕的开始。为什么你有一个包含数千行的标量变量?几乎可以肯定,您的代码中有一些较早的步骤应该被修复,这样您就不会处于这种情况。
  • @EdMorton:完全是我的想法。 $var 是相当复杂的 find 命令的输出。为了避免对所有子目录嵌套如此复杂的find,我形成了包含整个输出的$var。有什么更好的方法来做到这一点?附言甚至每个子目录的递归(嵌套)find -maxdepth 1 都比这个恕我直言更快。
  • 将它们保存到临时文件中如何?
  • @oguzismail:可以做到。您认为这种方法是正确执行此操作的最快方法吗?
  • 我进一步调查了这个问题,看看是否切换到使用文件而不是在变量中存储大量数据。似乎 bash 变量可以保存多少数据没有限制,除了操作系统设置的任何内容。但是,我可以使用mkfifo,如果/tmptmpfs 挂载,而且通常是这样,那么无论如何它都会使用内存。嗯.. 看来性能方面我在这里没有做错任何事情(将大量数据存储在变量中)?唯一严重的瓶颈似乎是对其进行多次排序,但@oguzismail 已经为它提供了一些解决方法。

标签: bash sorting awk uniq


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

这使用PCRE,因此需要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


万一上面提到的工具都不可用,这里有一个可移植的解决方案:

$ cat prog.awk
{
  ext = $NF
  sub(/[[:space:]][^[:space:]]+$/, "")
  if ($0 == 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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-10-08
    • 2012-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-09
    • 2019-03-20
    相关资源
    最近更新 更多