HOWTO · Bash
在 Bash 中运行 find -exec 命令
本文将演示如何结合使用 `find` 的 `-exec` 参数与 `grep`:`find` 用于筛选文件路径,`grep` 用于搜索文件内容。
本页内容
find 用于筛选文件系统路径;grep 用于搜索文件内容。-exec 操作会将两者连接起来,把每个筛选出的路径作为参数传给另一个命令。本指南介绍可复用的语法、内容搜索、逐个处理匹配项与批量传入路径的区别,以及谨慎执行文件操作的方法。
find -exec 的工作方式
find 从指定目录开始,根据文件类型、名称、修改时间或大小等条件进行检查,并访问符合条件的路径。使用 -exec 时,它会运行指定的命令,并将每个匹配路径替换到 {} 的位置。路径是作为参数传递的,因此文件名中的空格、制表符、引号和通配符不会被 shell 重新解析。与拼接命令字符串或将以换行符分隔的名称传给另一个命令相比,这种方式更安全。
搜索和操作是两项不同的工作:-name '*.txt' 这样的条件用于筛选文件,而 grep 这样的命令会读取文件内容。如果操作只应针对普通文件,请添加 -type f。默认情况下,find 也会检查目录,因此应选择符合命令输入要求的筛选条件。
find -exec 的基本语法
可复用的形式是 find STARTING_PATH TESTS -exec COMMAND {} \;。STARTING_PATH 指定要搜索的目录树;筛选条件决定哪些路径会传给操作;COMMAND 及其选项指定要执行的工作;{} 标记 find 插入路径的位置。在交互式 shell 中,结束符应写作 \;,这样 shell 才会将字面分号传给 find。如果不转义也不加引号,shell 会把分号当作命令分隔符。
对 '*.txt' 这样的文件名模式要加引号。引号会让 shell 原样传递该模式,使 find 能在遍历时匹配各路径中的名称,而不是让 shell 根据当前目录中的文件展开模式。对于以连字符开头的路径,应使用 ./-archive 这样的显式起始目录,避免路径被误认为命令选项。
运行以下示例:
本节中的代码以及显示的标签或输出仍为英文。
set -eu
root=$(mktemp -d)
trap 'rm -rf "$root"' EXIT
mkdir -p "$root/notes"
printf 'report: first\nkeep this line\n' > "$root/notes/weekly report.txt"
printf 'draft only\n' > "$root/notes/draft.txt"
find "$root" -type f -name '*.txt' -exec grep -nH 'report' {} +
该示例会自行创建一个小目录,只搜索文本文件,并让 grep 输出匹配行的行号和文件名。即使某次调用只收到一个路径,-H 也会保留文件名。shell 退出时会删除临时目录。这是一个可运行的示例;此处不声称有已捕获的输出。
使用 find 和 grep 搜索文件内容
若要搜索 /documents 下的文本,可使用 find /documents -type f -name '*.txt' -exec grep -nH 'report' {} +。find 会筛选出候选文本文件,grep 则输出每一条匹配行及其路径和行号。如果只需要包含匹配内容的文件名,可用 grep -l 替代 -nH。一个文件可能包含多条匹配行,因此按行输出时,同一路径可能会出现多次。
搜索字符串加了引号,因此会作为一个字面参数传入。如果要使用正则表达式,请明确写出表达式;如果要查找固定字符串,可使用 grep -F,避免将正则表达式元字符当作特殊语法处理。由于权限或文件编码问题,搜索可能无法读取某些文件;编写脚本时,应将命令的诊断信息和退出状态一并纳入判断。这里没有列出示例路径,因为匹配结果取决于读者目录中的文件。
如何选择 ; 或 +
使用 -exec COMMAND {} \; 时,find 会针对每个匹配路径分别运行一次命令。这种写法容易理解,适用于按单个路径处理文件的命令。经过 shell 转义的分号用于结束操作定义,并不是要运行的命令的一部分。
使用 -exec COMMAND {} + 时,find 会在每次调用中传入多个匹配路径,并将它们分成符合系统参数长度限制的组。{} 只写一次,放在 + 前面,作为命令的最后一个参数。对于 grep 等工具,批量传入路径通常能减少启动进程的开销,但不会改变这些工具对各个文件的处理方式;如果命令的含义取决于恰好接收一个路径,批量执行就不适用。加号会直接传给 find,无需 shell 转义。
安全地对匹配文件运行其他命令
删除旧日志前,先用 -print 替换操作并检查列出的路径:find /var/log -type f -name '*.log' -mtime +30 -print。-mtime +30 会筛选修改时间早于 30 个完整 24 小时周期的文件;它不表示“早于本月 30 日”。检查筛选结果并确认具备所需权限后,GNU 和许多其他 find 实现支持 -delete,例如 find /var/log -type f -name '*.log' -mtime +30 -delete。使用此命令删除的文件无法撤销,因此执行前请核实起始目录和筛选条件。
若要从下载目录树中移动大于 100 mebibyte 的普通文件,请先检查 find /downloads -type f -size +100M -print。如果列表符合预期,且 /archive/ 已存在于同一系统并具备适当权限,可逐个文件执行 find /downloads -type f -size +100M -exec mv -- {} /archive/ \;。+100M 大小条件使用 find 的大小单位,并向上取整到下一个单位;若需要精确的字节阈值,请确认所用实现的单位定义。移动文件可能覆盖目标文件,也可能与目标中已有的同名文件冲突,运行前请检查是否有重名文件。无论执行哪项操作,都应收紧筛选条件,并在更改文件前检查预览结果。