Wie finde ich, welche Dateien in einer Liste fehlen?


9

Ich habe eine Liste von Dateien, die ich überprüfen möchte, ob sie in meinem Dateisystem vorhanden sind. Ich dachte daran, dies findwie folgt zu tun :

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

(using zsh), aber das funktioniert nicht, da findes zu beenden scheint, 0ob es die Datei findet oder nicht. Ich glaube , ich es durch eine andere Prüfung bestehen , die zu sehen , ob testet findjede Ausgabe (roh , aber effektiv wäre das zu ersetzen , produziert > /dev/nullmit |grep '') , aber das fühlt sich an wie ein Troll mit einer Ziege (andere Nationalitäten sagen etwas über Vorschlaghämmern und Walnüsse könnte fangen ).

Gibt es eine Möglichkeit find, mich zu einem nützlichen Exit-Wert zu zwingen ? Oder zumindest, um eine Liste der Dateien zu erhalten, die nicht gefunden wurden? (Ich kann mir vorstellen, dass Letzteres durch eine raffinierte Auswahl logischer Verknüpfungen vielleicht einfacher ist, aber ich scheine immer in Knoten verwickelt zu sein, wenn ich versuche, es herauszufinden.)

Hintergrund / Motivation: Ich habe eine "Master" -Sicherung und möchte überprüfen, ob einige Dateien auf meinem lokalen Computer auf meiner Master-Sicherung vorhanden sind, bevor ich sie lösche (um etwas Speicherplatz zu schaffen). Also machte ich eine Liste der Dateien, sshbearbeitete sie auf dem Master-Computer und war dann ratlos, den besten Weg zu finden, um die fehlenden Dateien zu finden.


Ich habe meine Lösung aktualisiert, um sie weitaus schneller zu nutzen locate.
Benutzer unbekannt

@userunknown locatezeigt nicht den aktuellen Status des Dateisystems an, es kann ein Tag oder sogar eine Woche alt sein. Dies eignet sich als Basis zum Testen von Backups.
Volker Siegel

Antworten:


5

findbetrachtet das Finden von nichts als besonderen Erfolgsfall (es ist kein Fehler aufgetreten). Eine allgemeine Methode zum Testen, ob Dateien bestimmten findKriterien entsprechen, besteht darin, zu testen, ob die Ausgabe von findleer ist. Um eine bessere Effizienz bei übereinstimmenden Dateien zu erzielen, verwenden Sie -quitGNU find, um es beim ersten Abgleich zu beenden, oder head( head -c 1falls verfügbar, ansonsten head -n 1Standard) auf anderen Systemen, um es an einem kaputten Rohr zu sterben, anstatt eine lange Ausgabe zu erzeugen.

while IFS= read -r name; do
  [ -n "$(find . -name "$name" -print | head -n 1)" ] || printf '%s\n' "$name"
done <file_list

In bash ≥4 oder zsh benötigen Sie den externen findBefehl nicht für eine einfache Namensübereinstimmung : Sie können verwenden **/$name. Bash-Version:

shopt -s nullglob
while IFS= read -r name; do
  set -- **/"$name"
  [ $# -ge 1 ] || printf '%s\n' "$name"
done <file_list

Zsh-Version nach einem ähnlichen Prinzip:

while IFS= read -r name; do
  set -- **/"$name"(N)
  [ $# -ge 1 ] || print -- "$name"
done <file_list

Oder hier ist eine kürzere, aber kryptischere Methode, um die Existenz einer Datei zu testen, die einem Muster entspricht. Das Glob-Qualifikationsmerkmal Nmacht die Ausgabe leer, wenn keine Übereinstimmung [1]vorliegt , behält nur die erste Übereinstimmung bei und e:REPLY=true:ändert jede Übereinstimmung, die erweitert werden soll, 1anstelle des übereinstimmenden Dateinamens. Erweitert **/"$name"(Ne:REPLY=true:[1]) falsesich also darauf, true falseob es eine Übereinstimmung gibt oder falseob es keine Übereinstimmung gibt.

while IFS= read -r name; do
  **/"$name"(Ne:REPLY=true:[1]) false || print -- "$name"
done <file_list

Es wäre effizienter, alle Ihre Namen in einer Suche zu kombinieren. Wenn die Anzahl der Muster für die Längenbeschränkung Ihres Systems in einer Befehlszeile nicht zu groß ist, können Sie alle Namen mit -overknüpfen, einen einzelnen findAufruf tätigen und die Ausgabe nachbearbeiten. Wenn keiner der Namen Shell-Metazeichen enthält (so dass die Namen auch findMuster sind ), haben Sie folgende Möglichkeit, mit awk (ungetestet) nachzubearbeiten:

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

Ein anderer Ansatz wäre die Verwendung von Perl und File::Find, wodurch es einfach ist, Perl-Code für alle Dateien in einem Verzeichnis auszuführen.

perl -MFile::Find -l -e '
    %missing = map {chomp; $_, 1} <STDIN>;
    find(sub {delete $missing{$_}}, ".");
    print foreach sort keys %missing'

Ein alternativer Ansatz besteht darin, auf beiden Seiten eine Liste mit Dateinamen zu erstellen und einen Textvergleich durchzuführen. Zsh-Version:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)

Ich akzeptiere dies aus zwei Gründen. Ich mag die zshLösung mit der **Syntax. Es ist eine sehr einfache Lösung, und obwohl sie in Bezug auf die Maschine möglicherweise nicht die effizienteste ist , ist sie wahrscheinlich die effizienteste, wenn ich mich tatsächlich daran erinnere! Die erste Lösung hier beantwortet auch die eigentliche Frage dahingehend, dass sie sich findin etwas verwandelt , bei dem der Exit-Code "Ich habe eine Übereinstimmung" von "Ich habe keine Übereinstimmung" unterscheidet.
Andrew Stacey

9

Sie können verwenden, statum festzustellen, ob eine Datei im Dateisystem vorhanden ist.

Sie sollten die integrierten Shell-Funktionen verwenden , um zu testen, ob Dateien vorhanden sind.

while read f; do
   test -f "$f" || echo $f
done < file_list

Der "Test" ist optional und das Skript funktioniert tatsächlich ohne ihn, aber ich habe ihn zur besseren Lesbarkeit dort gelassen.

Bearbeiten: Wenn Sie wirklich keine andere Wahl haben, als für eine Liste von Dateinamen ohne Pfade zu arbeiten, empfehlen wir Ihnen, einmal mit find eine Liste von Dateien zu erstellen und diese dann mit grep zu durchlaufen, um herauszufinden, welche Dateien vorhanden sind.

find -type f /dst > $TMPFILE
while read f; do
    grep -q "/$f$" $TIMPFILE || echo $f
done < file_list

Beachten Sie, dass:

  • Die Dateiliste enthält nur Dateien, keine Verzeichnisse.
  • Der Schrägstrich im Grep-Übereinstimmungsmuster ist so, dass wir vollständige Dateinamen und keine Teilnamen vergleichen.
  • und das letzte '$' im Suchmuster soll mit dem Zeilenende übereinstimmen, damit Sie keine Verzeichnisübereinstimmungen erhalten, sondern nur vollständige Dateinamen-Patches.

stat braucht den genauen Ort, nicht wahr? Ich verwende find, weil ich nur eine Liste von Dateinamen habe und diese sich in zahlreichen Verzeichnissen befinden könnten. Entschuldigung, wenn das nicht klar war.
Andrew Stacey

Hmmm. Ja, du hast nicht gesagt, dass du Dateinamen ohne Pfade hast! Vielleicht können Sie stattdessen DIESES Problem beheben? Es wäre weitaus effizienter, als mehrere Male im selben Datensatz zu suchen.
Caleb

Vielen Dank für die Bearbeitung und nochmals Entschuldigung, dass Sie nicht spezifisch sind. Der Dateiname / -pfad wird nicht korrigiert - Dateien befinden sich möglicherweise an verschiedenen Stellen auf den beiden Systemen, daher möchte ich eine Lösung, die robust genug ist, um dies zu umgehen. Der Computer sollte nach meinen Vorgaben funktionieren , nicht umgekehrt! Im Ernst, das mache ich nicht oft - ich suchte nach alten Dateien zum Löschen, um Speicherplatz zu schaffen, und wollte nur einen "schnellen und schmutzigen" Weg, um sicherzustellen, dass sie in meinen Backups sind.
Andrew Stacey

Zunächst müssten Sie nicht den vollständigen Pfad angeben, sondern nur einen relativen Pfad zu der Verzeichnisstruktur, die Sie gesichert haben. Lassen Sie mich vorschlagen, dass wenn der Pfad nicht derselbe ist, die Wahrscheinlichkeit groß ist, dass die Datei nicht derselbe ist und Sie möglicherweise falsch positive Ergebnisse aus Ihrem Test erhalten. Es hört sich so an, als wäre Ihre Lösung eher schmutzig als schnell. Ich würde nicht wollen, dass du verbrannt wirst, wenn du denkst, du hättest etwas, was du nicht hast. Wenn Dateien wertvoll genug sind, um überhaupt gesichert zu werden, sollten Sie die Primärdaten nicht löschen, da Sie sonst Ihre Sicherungen sichern müssen!
Caleb

Ak! Ich habe eine Menge Details ausgelassen, um zu versuchen, die Frage zu fokussieren, und Sie füllen diese mit einer Menge Annahmen aus, die - ich sollte sagen - durchaus vernünftig sind, aber völlig falsch liegen! Es genügt zu sagen, dass ich weiß, dass wenn die Datei vorhanden ist und sich in einem Verzeichnis mit einem bestimmten Namenstyp befindet, ich weiß, dass es sich um die Originaldatei handelt und es sicher ist, die Kopie auf meinem Computer zu löschen.
Andrew Stacey

1

Ein erster, vereinfachender Ansatz könnte sein:

a) Sortieren Sie Ihre Dateiliste:

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

Missionen zu finden, oder

comm sorted.lst found.lst

Übereinstimmungen finden

  • Tücken:
    • Zeilenumbrüche in Dateinamen sind sehr schwer zu handhaben
    • Leerzeichen und ähnliche Dinge in Dateinamen sind auch nicht schön. Da Sie jedoch die Kontrolle über die Dateien in der Dateiliste haben, reicht diese Lösung möglicherweise bereits aus ...
  • Nachteile:

    • Wenn find eine Datei findet, wird sie weiter ausgeführt, um eine andere und eine andere zu finden. Es wäre schön, die weitere Suche zu überspringen.
    • find könnte mit einiger Vorbereitung nach mehreren Dateien gleichzeitig suchen:

      find -name a.file -oder -name -b.file -oder -name c.file ...

Könnte das Auffinden eine Option sein? Wiederum wird eine vorsortierte Liste von Dateien angenommen:

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

Eine Suche nach foo.bar stimmt nicht mit einer Datei foo.ba oder oo.bar mit dem Konstrukt --regexp überein (nicht durch Regex ohne p zu verwechseln).

Sie können eine bestimmte Datenbank für die Suche angeben und müssen diese vor der Suche aktualisieren, wenn Sie die neuesten Ergebnisse benötigen.


1

Ich denke, das kann auch nützlich sein.

Dies ist eine einzeilige Lösung, falls Sie sich für Ihre "Liste" als echte Dateien entscheiden, die Sie mit einem anderen Ordner synchronisieren möchten:

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

um beim Lesen zu helfen:

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

In diesem Beispiel werden Sicherungsdateien "* ~" ausgeschlossen und der reguläre Dateityp "-type f" beschränkt.


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}

Vielleicht?


0

Warum nicht einfach die Länge der Abfrageliste mit der Länge der Ergebnisliste vergleichen?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.