我有一个文件列表,我想检查它们是否存在于我的文件系统中。我想使用find
如下方法执行此操作:
for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done
(使用zsh
)但这不起作用,因为无论是否找到文件find
都会退出。0
我想我可以通过其他一些测试来测试它是否find
产生任何输出(粗略但有效的方法是替换> /dev/null
with |grep ''
),但这感觉就像使用巨魔来捕捉山羊(其他国家可能会说一些关于大锤和核桃的事情) )。
有没有办法强制find
给我一个有用的退出价值?或者至少获取那些文件的列表不是成立? (我可以想象,通过一些巧妙的逻辑连接词选择,后者可能会更容易,但当我试图弄清楚它时,我似乎总是陷入困境。)
背景/动机:我有一个“主”备份,我想在删除它们之前检查本地计算机上的某些文件是否存在于我的主备份中(以创建一点空间)。因此,我列出了文件列表,ssh
将它们编辑到主机上,然后不知如何找到丢失文件的最佳方法。
答案1
您可以用来stat
确定文件系统上是否存在文件。
您应该使用内置的外壳函数测试文件是否存在。
while read f; do
test -f "$f" || echo $f
done < file_list
“测试”是可选的,没有它脚本实际上也可以工作,但为了便于阅读,我将其留在那里。
编辑:如果您确实别无选择,只能处理没有路径的文件名列表,我建议您使用 find 构建一次文件列表,然后使用 grep 迭代它以找出其中有哪些文件。
find -type f /dst > $TMPFILE
while read f; do
grep -q "/$f$" $TIMPFILE || echo $f
done < file_list
注意:
- 文件列表仅包含文件而不包含目录,
- grep 匹配模式中的斜杠是这样我们比较完整的文件名而不是部分文件名,
- 搜索模式中的最后一个“$”用于匹配行尾,因此您不会获得目录匹配,而只会获得完整的文件名补丁。
答案2
find
认为没有发现任何成功的特殊情况(没有发生错误)。测试文件是否符合某些条件的通用方法find
是测试 的输出是否find
为空。为了在存在匹配文件时提高效率,请-quit
在 GNU find 上使用,使其在第一次匹配时退出,或者head
(head -c 1
如果可用,否则head -n 1
这是标准的)在其他系统上使用,使其因管道损坏而死亡,而不是产生长输出。
while IFS= read -r name; do
[ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list
在 bash ≥4 或 zsh 中,您不需要外部find
命令来进行简单的名称匹配:您可以使用**/$name
.重击版本:
shopt -s nullglob
while IFS= read -r name; do
set -- **/"$name"
[ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list
Zsh版本的原理类似:
while IFS= read -r name; do
set -- **/"$name"(N)
[ $# -ge 1 ] || print -- "$name"
done <file_list
或者这是一种更短但更神秘的方法来测试与模式匹配的文件是否存在。N
如果没有匹配项,glob 限定符会使输出为空,[1]
仅保留第一个匹配项,并将e:REPLY=true:
每个匹配项更改为扩展1
为而不是匹配的文件名。因此**/"$name"(Ne:REPLY=true:[1]) false
扩展为true false
是否存在匹配,或者仅扩展为false
不存在匹配。
while IFS= read -r name; do
**/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list
将所有姓名合并到一次搜索中会更有效。如果模式数量对于命令行上的系统长度限制来说不太大,则可以使用 来连接所有名称-o
,进行一次find
调用,并对输出进行后处理。如果没有一个名称包含 shell 元字符(因此名称也是find
模式),则可以使用以下方法使用 awk 进行后处理(未经测试):
set -o noglob; IFS='
'
set -- $(<file_list sed -e '2,$s/^/-o\
/')
set +o noglob; unset IFS
find . \( "$@" \) -print | awk -F/ '
BEGIN {while (getline <"file_list") {found[$0]=0}}
wanted[$0]==0 {found[$0]=1}
END {for (f in found) {if (found[f]==0) {print f}}}
'
另一种方法是使用 Perl 和File::Find
,这样可以轻松地为目录中的所有文件运行 Perl 代码。
perl -MFile::Find -l -e '
%missing = map {chomp; $_, 1} <STDIN>;
find(sub {delete $missing{$_}}, ".");
print foreach sort keys %missing'
另一种方法是生成两侧的文件名列表并进行文本比较。 Zsh 版本:
comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
答案3
第一种简单的方法可能是:
a) 对文件列表进行排序:
sort file.lst > sorted.lst
for f in $(< sortd.lst) ; do find -name $f -printf "%f\n"; done > found.lst
diff sorted.lst found.lst
寻找失踪者,或
comm sorted.lst found.lst
寻找匹配项
- 陷阱:
- 文件名中的换行符很难处理
- 文件名中的空格和类似内容也不好。但是,既然您可以控制文件列表中的文件,也许这个解决方案已经足够了,但是......
缺点:
- 当 find 找到一个文件时,它会继续运行以查找另一个文件,然后再查找另一个文件。如果能跳过进一步的搜索就好了。
find 可以一次搜索多个文件,需要做一些准备:
find -name a.file -or -name -b.file -or -name c.file ...
可以找到一个选项吗?同样,假设有一个预先排序的文件列表:
for f in $(< sorted.tmp) ; do locate --regexp "/"$f"$" > /dev/null || echo missing $f ; done
搜索 foo.bar 不会与带有 --regexp-construct 的文件 foo.ba 或 oo.bar 匹配(不要被不带 p 的正则表达式混淆)。
您可以指定一个特定的数据库进行查找,如果您需要最新的结果,则必须在搜索之前更新它。
答案4
FIND_EXP=". -type f \( "
while read f; do
FIND_EXP="${FIND_EXP} -iname $f -or"
done < file_list
FIND_EXP="${var%-or}"
FIND_EXP="${FIND_EXP} \)"
find ${FIND_EXP}
或许?