我想对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
代替:tail
wc
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'