我有一个文件列表,我想检查它们是否存在于我的文件系统中。我想这样做find:
for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done
Run Code Online (Sandbox Code Playgroud)
(使用zsh) 但这不起作用,因为无论是否找到文件find似乎都会退出0。我想我可以通过其他一些测试来测试是否find产生任何输出(粗略但有效的将是替换> /dev/nullwith |grep '')但这感觉就像使用巨魔捉山羊(其他国籍的人可能会说一些关于大锤和核桃的事情) )。
有没有办法强制find给我一个有用的退出值?或者至少要获取未找到的那些文件的列表?(我可以想象,通过一些巧妙的逻辑连接词选择,后者可能更容易,但当我试图弄清楚时,我似乎总是陷入困境。)
背景/动机:我有一个“主”备份,我想在删除它们之前检查我的本地机器上的某些文件是否存在于我的主备份中(以创建一些空间)。所以我制作了一个文件列表,将ssh它们编辑到主机上,然后我无法找出找到丢失文件的最佳方法。
您可以stat用来确定文件系统上是否存在文件。
您应该使用内置的shell 函数来测试文件是否存在。
while read f; do
test -f "$f" || echo $f
done < file_list
Run Code Online (Sandbox Code Playgroud)
“测试”是可选的,没有它脚本实际上也能工作,但为了可读性我把它留在了那里。
编辑:如果你真的别无选择,只能处理没有路径的文件名列表,我建议你用 find 构建一个文件列表,然后用 grep 迭代它以找出哪些文件存在。
find -type f /dst > $TMPFILE
while read f; do
grep -q "/$f$" $TIMPFILE || echo $f
done < file_list
Run Code Online (Sandbox Code Playgroud)
注意:
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
Run Code Online (Sandbox Code Playgroud)
在 bash ?4 或 zsh 中,您不需要外部find命令来进行简单的名称匹配:您可以使用**/$name. bash 版本:
shopt -s nullglob
while IFS= read -r name; do
set -- **/"$name"
[ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list
Run Code Online (Sandbox Code Playgroud)
Zsh 版本基于类似的原理:
while IFS= read -r name; do
set -- **/"$name"(N)
[ $# -ge 1 ] || print -- "$name"
done <file_list
Run Code Online (Sandbox Code Playgroud)
或者这里有一种更短但更神秘的方法来测试是否存在与模式匹配的文件。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
Run Code Online (Sandbox Code Playgroud)
将您的所有姓名合并为一次搜索会更有效。如果模式的数量对于命令行上的系统长度限制来说不是太大,您可以使用 连接所有名称-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}}}
'
Run Code Online (Sandbox Code Playgroud)
另一种方法是使用 Perl 和File::Find,这样可以轻松地为目录中的所有文件运行 Perl 代码。
perl -MFile::Find -l -e '
%missing = map {chomp; $_, 1} <STDIN>;
find(sub {delete $missing{$_}}, ".");
print foreach sort keys %missing'
Run Code Online (Sandbox Code Playgroud)
另一种方法是在两侧生成文件名列表并进行文本比较。zsh版本:
comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Run Code Online (Sandbox Code Playgroud)