假设我想对包含单词 的cmd
所有*.cpp
和文件执行。*.hpp
FOO
只要发现这些文件消失了,我知道我能做到,
find /path/to/dir -name '*.[hc]pp' -exec grep -l 'FOO' {} +
但是扩展处理以便我可以执行(例如cmd
对每个文件)执行的正确方法是什么?
我知道我可以做-exec bash -c '...'
并写下“如果文件内容包含FOO
,则cmd
对该文件运行”逻辑...
,但这感觉就像大炮射苍蝇。
答案1
-exec … \;
也是一个测试,只要里面的命令返回退出状态 0,它就成功。-exec … +
可以用一个命令处理许多路径名,并且它总是成功,所以这不是一个有用的测试。
grep -q
配合得很好,-exec … \;
因为当存在匹配时(即使检测到错误)它返回退出状态 0,否则返回 1。
将您的文件逐个测试,以便您可以添加另一个-exec grep … +
文件来有条件地运行您所需的命令:-exec grep -q … \;
-exec
find /path/to/dir -name '*.[hc]pp' -exec grep -q 'FOO' {} \; -exec …
一般来说,事实-exec … \;
是测试允许您构建自定义测试,您可以在其中测试几乎任何内容;特别是因为您可以运行sh -c
并因此使用管道、可以操作的变量、shell 条件等来实现测试(但请注意这一点:可以find -exec sh -c
安全使用吗?)。
答案2
使用这些实用程序的 GNU 实现(以及支持 ksh 样式的进程替换的 shell),您可以执行以下操作:
xargs -r0a <(grep -rlZ --include='*.[hc]pp' FOO) cmd --
上面, with -r
(又名--recursive
)grep
执行了find
的工作(请注意,在当前版本中grep
,它的行为就像-type f
传递给等效find
命令一样),并将l
NUL 分隔的文件路径列表 ( -Z
)xargs
通过管道传递给,管道的路径传递给-a
(又名--arg-file
)。
如果您仍然想使用find
(例如应用比文件名后缀更复杂的文件条件),您可以这样做:
xargs -r0a <(
find . -name '*.[hc]pp' -type f -exec grep -lZ FOO {} +
) cmd
(这里--
不是必需的,因为文件路径将以 开头./
,所以不是-
nor +
)。
执行find ... -exec grep -q FOO {} \; -exec cmd {} +
有效,但意味着分叉一个进程,执行grep
,这涉及到为每个文件加载和链接共享库,这比这里需要做的要昂贵几个数量级grep
(读取几 KiB 的文本并FOO
在其中查找),所以如果性能或资源使用是一个问题,最好尽可能避免。
如果您不在 GNU 系统²上,但您find
支持-print0
并xargs
支持下一版本-r
的-0
POSIX 标准,您可以使用以下方法报告文件路径perl
:
find . -name '*.[ch]pp' -type f -print0 |
xargs -r0 perl -0lne 'if (/FOO/} {print $ARGV; close ARGV}' |
xargs -r0 cmd
尽管这假设cmd
不从其 stdin 读取(此处,根据实现,xargs
将在 /dev/null 上打开或管道的读取端正perl
在写入,继承自xargs
)。
1 包括除基于 pdksh 和 zsh 和 bash 之外的 ksh 实现;在类似 rc 的 shell 中将更改为<(...)
,并在<{...}
(...|psub)
fish
² 但请注意,GNUgrep
曾经在 BSD 上使用(并且仍然在某些 BSD 上使用),并且一些已经脱离 GNU 的 BSDgrep
已经复制了其 API,因此您会发现其非 GNU 实现grep
确实支持其-Z
非标准选项;有时只有--null
长选项等效项,例如 OpenBSD(其中-Z
是其他内容)、FreeBSD 或 MacOS。
答案3
globstar
首先在 Bash 中打开该选项:
shopt -s globstar
现在您可以简单地执行以下操作:
for x in /path/to/dir/**/*.[hc]pp; do
if grep -q 'FOO' "$x"; then
...
fi
fi
该**
模式匹配零个或多个路径组件(如果后跟 则仅限于目录/
)。
因此该模式将找到/path/to/dir/foo.cpp
以及/path/to/dir/deeply/nested/foo.cpp
。
答案4
链接 xargs 的有趣案例:
find /path/to/dir -name '*.[hc]pp' -print0 | xargs -0 grep -lZ 'FOO' | xargs -0 ...
根据您的命令,您可能需要传递-n 1
给第二调用 来xargs
为每个文件运行一个命令,也可以不运行。
该find
命令生成一个以 0 分隔的匹配文件名的二进制列表,第一个xargs
命令将最小数量的grep
命令拼接在一起,以测试所有文件的匹配内容,生成一个与该模式匹配的以 0 分隔的二进制文件列表,第二个xargs
命令运行您需要的命令。