Shell: 在一个目录下查找列表中的文件

14

我有一个包含大约1000个文件名的列表,需要在一个目录及其子目录下进行搜索。有数百个子目录,其中有超过100万个文件。以下命令将运行1000次查找:

cat filelist.txt | while read f; do find /dir -name $f; done

有没有更快的方法来做到这一点?

4个回答

18
如果filelist.txt每行只有一个文件名:
find /dir | grep -f <(sed 's@^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt)

(-f选项意味着grep会在给定文件中搜索所有模式。)

解释<(sed 's@^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt)

<( ... )是称为进程替代,与$( ... )有些相似。这种情况等同于(但使用进程替换更整洁,也可能更快):

sed 's@^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt > processed_filelist.txt
find /dir | grep -f processed_filelist.txt

调用sed命令对filelist.txt文件的每一行运行s@^@/@s/$/$/s/\([\.[\*]\|\]\)/\\\1/g三个命令,并将其输出。这些命令将文件名转换为更适合用于grep的格式。

  • s@^@/@意味着在每个文件名前加上/。(^在正则表达式中表示“行首”)
  • s/$/$/意味着在每个文件名后加上$。(第一个$表示“行末”,第二个是字面上的$,然后由grep解释为“行末”)

这两条规则的组合意味着grep只会查找像.../<filename>这样的匹配项,这样a.txt就不会匹配./a.txt.backup./abba.txt

s/\([\.[\*]\|\]\)/\\\1/g会在每个点.、括号[]或星号*的出现之前加上\。Grep使用正则表达式,这些字符被视为特殊字符,但我们想让它们变成普通字符,因此需要转义它们(如果没有转义它们,则文件名a.txt将匹配类似于abtxt的文件)。

例如:

$ cat filelist.txt
file1.txt
file2.txt
blah[2012].txt
blah[2011].txt
lastfile

$ sed 's@^@/@; s/$/$/; s/\([\.[\*]\|\]\)/\\\1/g' filelist.txt
/file1\.txt$
/file2\.txt$
/blah\[2012\]\.txt$
/blah\[2011\]\.txt$
/lastfile$

Grep使用find命令的输出中的每一行作为搜索模式。


谢谢!<(sed 's@^@/@; s/$/$/; s/./\./' filelist.txt)>是什么意思? - Dagang
@Todd,我已经扩展了我的回答 :) - huon
1
你不应该费尽心思地试图通过编程重新创建模式。这样做容易出错,而且在模式语言的规范中通常存在一些灰色区域或扩展的可能性。在这种特殊情况下,我认为最好只是使用 grep -F -f FILE - Jo So

11

如果 filelist.txt 是一个纯文本列表:

$ find /dir | grep -F -f filelist.txt

如果 filelist.txt 是一个模式列表:

$ find /dir | grep -f filelist.txt

2

使用xargs(1)代替bash中的while循环可以更快地执行。

像这样:

xargs -a filelist.txt -I filename find /dir -name filename

请注意,如果filelist.txt中的文件名包含空格,请阅读xargs(1)手册中“DESCRIPTION”部分的第二段关于此问题的说明。基于一些假设,可以进行改进。例如,a.txt在filelist.txt中,并且您可以确保在/dir中只有一个a.txt实例。然后,您可以告诉find(1)在找到该实例时提前退出。
xargs -a filelist.txt -I filename find /dir -name filename -print -quit

另一种解决方案是,您可以预处理filelist.txt,将其制成类似于find(1)参数列表的形式。这将减少find(1)的调用次数:

find /dir -name 'a.txt' -or -name 'b.txt' -or -name 'c.txt'

1
xargs -I 将被调用1000次。我刚在CentOS 7中测试了这个命令。 -I 开关将 xargs 切换到每行输入模式。从手册中可以看到: 未引用的空格不会终止输入项;相反,分隔符是换行符。意味着-x和-L 1。 在BSD(MacOS)中,-J 可以正确地打包命令行,但GNU xargs 不支持 -J - Dima Korobskiy

1

我不太确定这个问题的意思,但是我来到这个页面是为了寻找一种方法来发现哪4个文件在13000个文件中未能成功复制。

两个答案都没有解决我的问题,所以我做了以下操作:

cp file-list file-list2
find dir/ >> file-list2
sort file-list2 | uniq -u

这导致了我需要的4个文件列表。

思路是将两个文件列表合并以确定唯一条目。sort用于使重复条目相邻,这是uniq过滤它们的唯一方法。


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