如何对名称与模式匹配且内容与模式匹配的所有文件执行命令?

如何对名称与模式匹配且内容与模式匹配的所有文件执行命令?

假设我想对包含单词 的cmd所有*.cpp和文件执行。*.hppFOO

只要发现这些文件消失了,我知道我能做到,

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(又名--recursivegrep执行了find的工作(请注意,在当前版本中grep,它的行为就像-type f传递给等效find命令一样),并将lNUL 分隔的文件路径列表 ( -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支持-print0xargs支持下一版本-r-0POSIX 标准,您可以使用以下方法报告文件路径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命令运行您需要的命令。

相关内容