如何找到列表中缺少的文件?


9

我有一个文件列表,我想检查它们是否存在于我的文件系统中。我想这样做find是因为:

for f in $(cat file_list); do
find . -name $f > /dev/null || print $f
done

(使用zsh),但无论是否找到文件,find似乎都无法退出0。我想我可以通过它通过一些其他的测试,测试,看是否find产生任何输出(原油,但有效的将是取代> /dev/null|grep ''),但这种感觉就像使用一个巨魔赶山羊(其他国籍可能会说一些关于大锤和核桃)。

有没有一种方法可以强制find给我有用的退出价值?还是至少要获取找到的那些文件的列表?(我可以想象,通过一些狡猾的逻辑连接词的选择,后者可能会更容易,但是当我试图弄清楚时,我似乎总是被束缚。)

背景/动机:我有一个“主”备份,我想在删除本地计算机上的某些文件之前检查它们是否存在(这会产生一些空间)。因此,我列出了文件列表,将ssh它们编辑到主计算机上,然后却迷失了寻找丢失文件的最佳方法。


我更新了解决方案以更快地使用locate
用户未知

@userunknown locate没有显示文件系统的当前状态,可能是一天,甚至一个星期。这适合作为测试备份的基础。
Volker Siegel

Answers:


5

find认为没有发现任何成功的特殊情况(没有发生错误)。测试文件是否符合某些find条件的一般方法是测试的输出是否find为空。为了在有匹配文件时提高效率,请-quit在GNU find上使用它来使它在第一次匹配时退出,或者在其他系统上headhead -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

或者,这是测试与模式匹配的文件是否存在的一种更简短但更隐秘的方法。glob限定符N将在没有匹配项的情况下使输出为空,[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调用,然后对输出进行后处理。如果名称中都不包含外壳元字符(因此名称也包含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)

我接受这个有两个原因。我喜欢语法的zsh解决方案**。这是一个非常简单的解决方案,虽然它可能不是最有效的方面机器,它可能是最有效的其实记住它我的条件!另外,这里的第一个解决方案回答了实际问题,因为它会find变成退出代码将“我有比赛”与“我没有比赛”区分开的东西。
Andrew Stacey

9

您可以stat用来确定文件系统上是否存在文件。

您应该使用内置的shell函数来测试文件是否存在。

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匹配模式中的斜杠是,所以我们比较完整文件名而不是部分文件名,
  • 搜索模式中的最后一个'$'是匹配行的末尾,因此您不会得到目录匹配,而只会得到完整的文件名补丁。

stat需要确切的位置,不是吗?我使用find是因为我只有一个文件名列表,它们可能在许多目录中。很抱歉,如果不清楚。
Andrew Stacey

嗯 是的,您不是说文件名没有路径!也许您可以改正那个问题?这将比在同一数据集中运行查找多次的效率更高。
Caleb

感谢您的修改,并再次抱歉没有具体说明。文件名/路径不是我要解决的问题-文件可能位于两个系统上的不同位置,因此我需要一个足够健壮的解决方案来解决该问题。电脑应该可以按照我的规格工作,而不是相反!严重的是,这不是我经常做的事情-我一直在寻找一些要删除的旧文件以腾出空间,而只是想用一种“快速的处理”方式来确保它们在我的备份中。
Andrew Stacey

首先,您不需要完整路径,只需完整路径即可备份您要备份的任何目录结构。请允许我建议,如果路径不相同,则很有可能文件不相同,并且您可能会从测试中得到误报。听起来您的解决方案可能比快速解决方案更肮脏;我不想看到您以为自己有没有的东西而被烧死。另外,如果文件首先具有足够的价值以进行备份,则您不应该删除主文件,否则需要备份备份!
Caleb

啊!我遗漏了大量细节,试图集中讨论这个问题,而您正在用大量假设填充这些假设-我应该说-完全合理,但恰恰是完全错误的!可以说我知道如果文件存在并且在具有特定名称类型的目录中,那么我知道它是原始文件,可以安全地在我的机器上删除副本。
Andrew Stacey

1

第一种简单的方法可能是:

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可以通过一些准备立即搜索多个文件:

      找到-name a.file-或-name -b.file-或-name c.file ...

可以选择吗?再次假设文件的预排序列表:

 for f in $(< sorted.tmp) ; do locate --regexp "/"$f"$" > /dev/null || echo missing $f ; done

搜索foo.bar不会将文件foo.ba或oo.bar与--regexp-construct匹配(不带p的regex不会混淆)。

您可以指定一个特定的数据库进行查找,如果需要最新的结果,则必须在搜索之前进行更新。


1

我认为这也很有用。

如果您选择“列表”为要与另一个文件夹同步的真实文件,则这是一种解决方案:

function FUNCsync() { local fileCheck="$synchronizeTo/$1"; if [[ ! -f "$fileCheck" ]];then echo "$fileCheck";fi; };export -f FUNCsync;find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

帮助阅读:

function FUNCsync() {
  local fileCheck="$synchronizeTo/$1";
  if [[ ! -f "$fileCheck" ]];then 
    echo "$fileCheck";
  fi; 
};export -f FUNCsync;
find "$synchronizeFrom/" -maxdepth 1 -type f -not -iname "*~" -exec bash -c 'FUNCsync "{}"' \; |sort

此示例排除了备份“ *〜”文件,并限制了常规文件类型“ -type f”


0
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}

也许?


0

为什么不简单地将查询列表的长度与结果列表的长度进行比较?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.