使用find命令但排除两个目录中的文件

113

我想找到以 _peaks.bed 结尾的文件,但要排除 tmpscripts 文件夹中的文件。

我的命令如下:

 find . -type f \( -name "*_peaks.bed" ! -name "*tmp*" ! -name "*scripts*" \)

但是它并没有起作用。在 tmpscript 文件夹中的文件仍然会被显示。

有人对此有什么想法吗?

7个回答

227

以下是如何使用find指定:

find . -type f -name "*_peaks.bed" ! -path "./tmp/*" ! -path "./scripts/*"

说明:

  • find . - 从当前工作目录开始查找(默认情况下递归)
  • -type f - 指定只在结果中显示文件
  • -name "*_peaks.bed" - 查找文件名以_peaks.bed结尾的文件
  • ! -path "./tmp/*" - 排除所有路径以./tmp/开头的结果
  • ! -path "./scripts/*" - 还要排除所有路径以./scripts/开头的结果

测试解决方案:

$ mkdir a b c d e
$ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
$ find . -type f ! -path "./a/*" ! -path "./b/*"

./d/4
./c/3
./e/a
./e/b
./e/5

你离正确很近啦,-name 选项只考虑了基础名称,而 -path 考虑了整个路径 =)


干得好。但是,你忘记了原帖中想要的其中一件事情,即查找以“_peaks.bed”结尾的文件。 - alex
2
这个使用了GNU find中的许多扩展,但由于问题标记为Linux,所以这不是一个问题。好答案。 - Jonathan Leffler
2
一个小提示:如果在您的初始查找提示符中使用“。”,则必须在每个排除的路径中都使用它。路径匹配非常严格,不会进行模糊搜索。因此,如果您使用“find / -type f -name *.bed ! -path "./tmp/"”,它将无法正常工作。您需要使用“!-path“/tmp””才能使其正常工作。 - peelman
3
需要注意的是,* 在这里很重要。$ ! -path "./directory/*" - Thomas Bennett
8
根据man手册:“要忽略整个目录树,请使用-prune而不是检查树中的每个文件。”如果您要排除的目录层级很深,或者包含大量文件并且您关心性能,则应改用-prune选项。 - thdoan
我正在尝试根据您的解决方案适应一个变量排除列表这里,但没有成功。 - Jordan Mackie

17

使用

find \( -path "./tmp" -o -path "./scripts" \) -prune -o  -name "*_peaks.bed" -print
或者
find \( -path "./tmp" -o -path "./scripts" \) -prune -false -o  -name "*_peaks.bed"
或者
find \( -path "./tmp" -path "./scripts" \) ! -prune -o  -name "*_peaks.bed"

顺序很重要,它从左到右进行评估。 始终从路径排除开始。

解释

不要使用-not(或!)来排除整个目录。使用-prune。 如手册中所述:

−prune    The primary shall always evaluate as  true;  it
          shall  cause  find  not  to descend the current
          pathname if it is a directory.  If  the  −depth
          primary  is specified, the −prune primary shall
          have no effect.

并且在GNU查找手册中:

-path pattern
              [...]
              To ignore  a  whole
              directory  tree,  use  -prune rather than checking
              every file in the tree.

实际上,如果您使用-not -path "./pathname",find将对"./pathname"下每个节点评估表达式。

find表达式只是条件评估。

  • \( \) - 组操作(您可以使用-path "./tmp" -prune -o -path "./scripts" -prune -o,但这更冗长)。
  • -path "./script" -prune - 如果-path返回true并且是目录,则为该目录返回true,并且不要进入其中。
  • -path "./script" ! -prune - 它作为(-path "./script") AND (! -prune)评估。它撤销了修剪的“始终为真”,使其始终为假。 它避免将"./script"打印为匹配项。
  • -path "./script" -prune -false - 由于-prune始终返回true,因此您可以在其后面跟随-false执行与!相同的操作。
  • -o - OR运算符。如果在两个表达式之间未指定运算符,则默认为AND运算符。

因此,\( -path "./tmp" -o -path "./scripts" \) -prune -o -name "*_peaks.bed" -print会扩展为:

[ (-path "./tmp" OR -path "./script") AND -prune ] OR ( -name "*_peaks.bed" AND print )

打印在这里很重要,因为如果没有它,它就会被展开为:

{ [ (-path "./tmp" OR -path "./script" )  AND -prune ]  OR (-name "*_peaks.bed" ) } AND print

-print 是由 find 命令添加的,这就是为什么大多数情况下,您不需要在表达式中添加它。而且由于 -prune 返回真值,它将打印“./script”和“./tmp”。

在其他情况下,我们不需要使用-print,因为我们将-prune切换为始终返回false值。

提示:您可以使用 find -D opt expr 2>&1 1>/dev/null 来查看它是如何被优化和扩展的,
find -D search expr 2>&1 1>/dev/null 查看检查的路径。


如果没有使用“-name”谓词,似乎无法正常工作 - 即在使用“-type f”查找文件类型时。我会收到错误find: paths must precede expression - Hashim Aziz
1
@HashimAziz 我无法重现。使用find \( -path <path> -o -path <path> \) -prune -false -o -type f 在我的一边可以正常工作。如果我忘记在命令中加入 - (例如输入 type f 而不是 -type f),我会收到类似的错误消息。你尝试运行哪个命令? - f380cedric
是的,结果是我的语法有问题,我需要在那里添加一个“-not”。我会删除我的评论。 - Hashim Aziz
我花了几个小时来寻找同样的问题的答案,我也想理解我所做的事情,而不仅仅是复制别人的陈述。这绝对是我找到的最好的解释。非常感谢。 - luukburger
"./tmp""/.scripts"只有在当前目录的直接子目录下才能工作。使用"*/tmp""*/scripts"可以使它们在子目录中(递归)也能正常工作。 - M Imam Pratama
@MImamPratama 对于这些情况,您可以使用\(-name tmp -or -name scripts\) - Bob

8
这是一种可能的方法...
find . -type f -name "*_peaks.bed" | egrep -v "^(./tmp/|./scripts/)"

2
这个方法的优点是可以与任何版本的“find”一起使用,而不仅仅是GNU“find”。但是,由于问题标记为Linux,所以这并不重要。 - Jonathan Leffler

1
你可以尝试以下内容:
find ./ ! \( -path ./tmp -prune \) ! \( -path ./scripts -prune \) -type f -name '*_peaks.bed'

3
在像这样一个老问题上(4年了!),你想要解释为什么这个新答案更好或不同,而不仅仅是“倾倒”代码。 - Nic3500

1
通过这些解释,您可以达到您的目标和许多其他目标。只需按照您想要的方式连接每个部分即可。
模型
find ./\
 -iname "some_arg" -type f\ # File(s) that you want to find at any hierarchical level.
 ! -iname "some_arg" -type f\ # File(s) NOT to be found on any hirearchic level (exclude).
 ! -path "./file_name"\ # File(s) NOT to be found at this hirearchic level (exclude).
 ! -path "./folder_name/*"\ # Folder(s) NOT to be found on this Hirearchic level (exclude).
 -exec grep -IiFl 'text_content' -- {} \; # Text search in the content of the found file(s) being case insensitive ("-i") and excluding binaries ("-I").

例子

find ./\
 -iname "*" -type f\
 ! -iname "*pyc" -type f\
 ! -path "./.gitignore"\
 ! -path "./build/*"\
 ! -path "./__pycache__/*"\
 ! -path "./.vscode/*"\
 ! -path "./.git/*"\
 -exec grep -IiFl 'title="Brazil - Country of the Future",' -- {} \;

谢谢!

[参考文献:https://unix.stackexchange.com/q/73938/61742]


额外信息:

您可以将上述命令与您喜欢的编辑器一起使用,并分析找到的文件的内容,例如...

vim -p $(find ./\
 -iname "*" -type f\
 ! -iname "*pyc" -type f\
 ! -path "./.gitignore"\
 ! -path "./build/*"\
 ! -path "./__pycache__/*"\
 ! -path "./.vscode/*"\
 ! -path "./.git/*"\
 -exec grep -IiFl 'title="Brazil - Country of the Future",' -- {} \;)

1

对我而言,这个解决方案在使用 find 命令执行时无法正常工作,我不知道原因,所以我的解决方案是:

find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;

解释:与sampson-chen的相同,但增加了以下内容

-prune - 忽略前面的路径...

-o - 然后如果没有匹配项则打印结果(修剪目录并打印剩余结果)

18:12 $ mkdir a b c d e
18:13 $ touch a/1 b/2 c/3 d/4 e/5 e/a e/b
18:13 $ find . -type f -path "./a/*" -prune -o -path "./b/*" -prune -o -exec gzip -f -v {} \;

gzip: . is a directory -- ignored
gzip: ./a is a directory -- ignored
gzip: ./b is a directory -- ignored
gzip: ./c is a directory -- ignored
./c/3:    0.0% -- replaced with ./c/3.gz
gzip: ./d is a directory -- ignored
./d/4:    0.0% -- replaced with ./d/4.gz
gzip: ./e is a directory -- ignored
./e/5:    0.0% -- replaced with ./e/5.gz
./e/a:    0.0% -- replaced with ./e/a.gz
./e/b:    0.0% -- replaced with ./e/b.gz

接受的答案没有起作用,但这个方法有效。使用修剪(prune)命令,find . -path ./scripts -prune -name '*_peaks.bed' -type f。不确定如何排除多个目录。即使指定了 type,这也会列出顶级被排除的目录。除非你想要使用修剪来加速查找操作,否则通过 Grep 进行排除似乎更直接。 - Mohnish
我也遇到了排除多个目录的问题,但是上面的评论给了我一个有效的答案。我使用多个“-not -path”的实例,并在每个路径表达式中包含在“find”第一个参数中使用的完整前缀,并以星号结尾(并转义任何点)。 - jetset

0

尝试类似这样的东西

find . \( -type f -name \*_peaks.bed -print \) -or \( -type d -and \( -name tmp -or -name scripts \) -and -prune \)

如果目标是执行(而不是打印),只需在原地替换即可,如果我有点错误,请不要太惊讶。


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