如何在Linux的“find”命令输出中排除与特定模式匹配的目录?

6
我想要使用正则表达式结合Linux的find命令,递归地进入一个巨大的目录树,显示所有的.c、.cpp和.h文件,但省略包含某些子字符串的匹配项。最终,我希望将输出发送到xargs命令上,对所有匹配的文件进行特定处理。我可以通过管道将find的输出通过grep过滤以删除包含这些子字符串的匹配项,但对于包含空格的文件名,这种解决方法效果不佳。因此,我尝试使用find的-print0选项,将每个文件名终止符改为一个nul字符而不是一个换行符(空格),并使用xargs -0来期望接收nul分隔的输入而不是以空格分隔的输入,但我无法成功地将nul分隔的find传递给管道grep过滤器;grep -Z在这方面似乎没有起到帮助作用。

所以我想写一个更好的find regex,取消中间的grep过滤器……也许sed是一种替代方法?

在任何情况下,以下是一小部分目录的示例:

./barney/generated/bam bam.h
./barney/src/bam bam.cpp
./barney/deploy/bam bam.h
./barney/inc/bam bam.h
./fred/generated/dino.h
./fred/src/dino.cpp
./fred/deploy/dino.h
./fred/inc/dino.h

我希望输出包括所有.h、.c和.cpp文件,但不包括出现在“generated”和“deploy”目录中的文件。
顺便说一下,你可以通过将整行复制并粘贴到你的bash shell中来创建一个完整的测试目录(命名为fredbarney),以测试此问题的解决方案:
mkdir fredbarney; cd fredbarney; mkdir fred; cd fred; mkdir inc; mkdir docs; mkdir generated; mkdir deploy; mkdir src; echo x > inc/dino.h; echo x > docs/info.docx; echo x > generated/dino.h; echo x > deploy/dino.h; echo x > src/dino.cpp; cd ..; mkdir barney; cd barney; mkdir inc; mkdir docs; mkdir generated; mkdir deploy; mkdir src; echo x > 'inc/bam bam.h'; echo x > 'docs/info info.docx'; echo x > 'generated/bam bam.h'; echo x > 'deploy/bam bam.h'; echo x > 'src/bam bam.cpp'; cd ..;

这个命令可以找到所有的.h、.c和.cpp文件...
find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$"

...但是,如果我通过xargs传输它的输出,'bam bam'文件将被视为两个单独的(不存在的)文件名(请注意,这里我只是使用ls作为我的实际输出处理方式的替代品):

$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" | xargs -n 1 ls
ls: ./barney/generated/bam: No such file or directory
ls: bam.h: No such file or directory
ls: ./barney/src/bam: No such file or directory
ls: bam.cpp: No such file or directory
ls: ./barney/deploy/bam: No such file or directory
ls: bam.h: No such file or directory
ls: ./barney/inc/bam: No such file or directory
ls: bam.h: No such file or directory
./fred/generated/dino.h
./fred/src/dino.cpp
./fred/deploy/dino.h
./fred/inc/dino.h

使用findxargs命令,我们可以通过添加-print0和-0参数来增强功能:

$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" -print0 | xargs -0 -n 1 ls
./barney/generated/bam bam.h
./barney/src/bam bam.cpp
./barney/deploy/bam bam.h
./barney/inc/bam bam.h
./fred/generated/dino.h
./fred/src/dino.cpp
./fred/deploy/dino.h
./fred/inc/dino.h

这很好,但我不想在输出中包含“generated”和“deploy”目录。因此,我尝试了以下方法:

$ find . -regextype posix-egrep -regex ".+\.(c|cpp|h)$" -print0 | grep -v generated | grep -v deploy | xargs -0 -n 1 ls
barney  fred

使用 grep 后发现它无法正常工作,随后我尝试使用 -Z 选项(并不确定该选项的确切作用),但是这个选项也无法解决问题。为此,我想自己编写一个更好的正则表达式来使用 find 命令,以下是我得到的最佳方案:

find . -regextype posix-egrep -regex "(?!.*(generated|deploy).*$)(.+\.(c|cpp|h)$)" -print0 | xargs -0 -n 1 ls

然而bash不接受这个命令(出现"!.*: event not found"错误),即使那不是问题,我的正则表达式在我通常使用的正则表达式测试网页上也不能正确工作。

你有什么想法可以让这个正则表达式正常工作吗?这是我想要的输出结果:

$ find . [----options here----] | [----maybe grep or sed----] | xargs -0 -n 1 ls
./barney/src/bam bam.cpp
./barney/inc/bam bam.h
./fred/src/dino.cpp
./fred/inc/dino.h

……而我希望避免脚本和临时文件,这可能是我的唯一选择。

提前感谢! -Mark


2
“event not found” 的原因是 bash! 解释为历史扩展请求。在包含该字符的字符串中使用单引号或进行额外转义。我建议使用单引号! - sorpigal
2个回答

10

这个对我有效:

find . -regextype posix-egrep -regex '.+\.(c|cpp|h)$' -not -path '*/generated/*' \
       -not -path '*/deploy/*' -print0 | xargs -0 ls -L1d

我的改动很少:我单独添加了一些路径模式的排除,因为这样更容易操作,并且我用单引号将其包裹起来以避免被Shell解析。

“event not found”错误是由于bash将感叹号!解释为历史扩展请求。解决方法是使用单引号而不是双引号。

小测验:在sh中的单引号字符串中,哪些字符是特殊的?

答案:只有'是特殊的(它表示字符串结束)。这是最安全的做法。

grep使用-Z(有时称为--null)让grep输出以空字符而不是换行符结尾。你需要使用-z(有时称为--null-data),让grep将输入中的空字符解释为行结束符而不是换行符。这使得它能够正常处理find ... -print0的输出,后者会在每个文件名之后添加一个空字符而不是换行符。

如果您按照以下方式执行:

find . -regextype posix-egrep -regex '.+\.(c|cpp|h)$' -print0 | \
    grep -vzZ generated | grep -vzZ deploy | xargs -0 ls -1Ld

如果使用null作为分隔符,grep 的输入和输出将会被正确处理... 直到你的源文件之一以 deployment.cpp 命名并开始因为脚本而被“神秘地”排除。

顺便说一下,这里有一种更好的方法来生成您的测试用例文件集。

while read -r file ; do
    mkdir -p "${file%/*}"
    touch "$file"
done <<'DATA'
./barney/generated/bam bam.h
./barney/src/bam bam.cpp
./barney/deploy/bam bam.h
./barney/inc/bam bam.h
./fred/generated/dino.h
./fred/src/dino.cpp
./fred/deploy/dino.h
./fred/inc/dino.h
DATA

既然我已经这样做了来验证,那我想分享它并帮助你避免重复。不要重复做同样的事情! 计算机就是为此而存在的。


0

您的命令:

find . -regextype posix-egrep -regex "(?!.*(generated|deploy).*$)(.+\.(c|cpp|h)$)" -print0 | xargs -0 -n 1 ls

失败是因为您尝试使用不支持环视/回顾等功能的Posix扩展正则表达式https://superuser.com/a/596499/658319

find支持pcre,因此如果您转换为pcre,这应该可以工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接