リストから欠落しているファイルを見つけるにはどうすればよいですか?


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はファイルシステムの現在の状態を表示していません。1日または1週間前の可能性があります。これは、バックアップをテストするためのベースとして適しています。
Volker Siegel

回答:


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

すべての名前を1つの検索に組み合わせる方が効率的です。コマンドラインでのシステムの長さの制限に対してパターンの数が多すぎない場合は、すべての名前を-oで結合し、1回の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}}}
'

もう1つのアプローチはFile::Find、Perlおよびを使用することです。これにより、ディレクトリ内のすべてのファイルに対して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)

これを受け入れる理由は2つあります。構文のあるzshソリューションが好き**です。これは非常に単純なソリューションであり、マシンの点では最も効率的ではないかもしれませんが、実際にそれを覚えているという点ではおそらく最も効率的です!また、ここでの最初の解決策は、終了コードが「一致した」と「一致しなかった」とを区別する何かにねじ込まれるという点で、実際の質問に答えますfind
Andrew Stacey

9

を使用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一致パターンのスラッシュは、部分ファイルではなく完全なファイル名を比較するためのものです。
  • そして、検索パターンの最後の「$」は行の最後に一致するため、ディレクトリの一致は取得されず、完全なファイル名のパッチのみが取得されます。

statは正確な場所を必要としますか?ファイル名のリストがあり、それらが多数のディレクトリに存在する可能性があるため、私は検索を使用しています。それが明確でない場合は申し訳ありません。
Andrew Stacey

うーん。パスのないファイル名があるとは言いませんでした!多分あなたは代わりにその問題を修正できますか?同じデータセットで何度もfindを実行するよりもはるかに効率的です。
カレブ

編集ありがとうございます。具体的でなくて申し訳ありません。ファイル名/パスは私が修正するものではありません-ファイルは2つのシステムの異なる場所にある可能性があるので、それを回避するのに十分堅牢なソリューションが必要です。コンピュータは私の仕様で動作するはずですが、逆ではありません!真剣に、これは私が頻繁に行うことではありません-私はスペースを作るために削除する古いファイルを探していて、それらが私のバックアップにあることを確認するための「迅速かつ汚い」方法を望んでいました。
Andrew Stacey

まず最初に、フルパスを指定する必要はなく、バックアップするディレクトリ構造への相対パスを指定するだけです。私がいることを示唆することを許可するパスが同じでない場合、ファイルは同じではありません良いチャンスがあり、あなたのテストのうち、偽陽性を得るかもしれないが。あなたの解決策は、迅速というより汚いかもしれません。私はあなたがあなたが持っていない何かを持っていたと思ってあなたがやけどしたのを見たくありません。また、ファイルが最初からバックアップするのに十分な価値がある場合は、プライマリを削除しないでください。削除しないと、バックアップをバックアップする必要があります。
カレブ2011

あっ!質問に焦点を当てようとするために多くの詳細を省略しましたが、あなたはそれらを多くの仮定で満たしています-私は言うべきですが-完全に合理的ですが、たまたま完全に間違っています!ファイルがそこにあり、特定のタイプの名前のディレクトリにある場合は、それが元のファイルであり、マシン上のコピーを削除しても安全であることを知っていると言えば十分でしょう。
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は、いくつかの準備をして、一度に複数のファイルを検索できます。

      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の検索は、ファイルfoo.ba、またはoo.barを--regexp-constructと一致させません(pなしの正規表現で混乱しないでください)。

最新の結果が必要な場合は、特定のデータベースを指定して検索する前にデータベースを更新する必要があります。


1

これも役に立つと思います。

これは、「リスト」を別のフォルダーと同期させたい実際のファイルにすることを選択した場合の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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.