使用 find exec 计算嵌套在子目录中的 csv 文件中的行数

使用 find exec 计算嵌套在子目录中的 csv 文件中的行数

我想对find一些嵌套 csv 文件的结果运行两个管道命令,但我惨败了。

这个想法是这样的:

$ find ./tmp/*/ -name '*.csv' -exec tail -n +2 {} | wc -l \;

为了不是计算每个 CSV 文件的标题行。

命令失败:

wc: ';': No such file or directory
find: missing argument to `-exec'

for在这种情况下我真的需要循环吗?
例如:

$ for f in ./tmp/*/*.csv; do tail -n +2 ${f} | wc -l; done

但这样我就失去了find包含文件名的良好输出。

使用此解决方案时我也丢失了文件名:find -exec 中的管道命令?

$ find ./tmp/*/ -type f -name "*.csv" -print0 | while IFS= read -d '' f; do tail -n +2 "${f}" | wc -l; done

精确一点;当我谈到打印的文件名时,这是因为我习惯在单个文件上调用命令时出现以下结果:

$ tail -n +2 | wc -l ./tmp/myfile.csv 
2434 ./tmp/myfile.csv

我使用Ubuntu 18.04。

答案1

如果你写

find ... -exec foo | bar \;

竖线在调用之前由 shell 解释find。结果管道的左手是find ... -exec foo,这显然给出了“‘-exec’缺少参数”错误;管道的右手是bar

保护垂直杆免受外壳伤害,如

find ... -exec foo \| bar \;

没有帮助,因为后面的第一个标记-exec被解释为find命令,并且所有后续标记,直到(但不包括);+终止符,都被视为该命令的参数。

了解“find”的 -exec 选项以获得彻底的解释。

要使用管道,-exec您需要调用 shell。例如:

find ./tmp/*/ -name '*.csv' -exec sh -c '
  printf "%s %s\n" "$(tail -n +2 "$1" | wc -l)" "$1"' mysh {} \;

然后,为了避免出现“参数列表太长”错误的风险,./tmp/*/可以重写为

find ./tmp -path './tmp/*/*' ...

或者,更准确地说,也排除tmp的隐藏子目录(./tmp/*/默认情况下可能会这样做),如

find ./tmp -path './tmp/.*' -prune -o -path './tmp/*/*' ...

最后,您可以使用更快的-exec ... {} +变体,这可以避免为任何单个找到的文件调用 shell。例如,用andawk代替:tailwc

find ./tmp -path './tmp/.*' -prune -o -path './tmp/*/*' \
  -name '*.csv' -exec awk '
    BEGIN { skip = 1 }
    FNR > skip { lc[FILENAME] = (FNR - skip) }
    END { for (f in lc) print lc[f],f }' {} +

(请注意,awk还计算那些不以换行符结尾的格式错误的行,而不wc以换行符结尾)。

答案2

如果您想要的只是从每个中减去 1 wc -l,那么这非常简单明了:

find [whatever you want] -exec wc -l {} + | perl -pe 's/(\d+)/$1-1/e'

相关内容