Jak znaleźć pliki, których brakuje na liście?

9

Mam listę plików, które chcę sprawdzić, czy istnieją w moim systemie plików. Myślałem o zrobieniu tego za pomocą find:

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

(za pomocą zsh), ale to nie działa, ponieważ findwydaje się, że kończy działanie 0niezależnie od tego, czy znajdzie plik. Chyba mogę przekazać go za pośrednictwem innego testu, który sprawdza, czy findprodukuje żadnego wyjścia (surowy, ale skuteczne byłoby wymienić > /dev/nullz |grep ''), ale to uczucie za pomocą troll złapać kozę (inne narodowości mogą powiedzieć coś o młotami i orzechami ).

Czy istnieje sposób, aby przekonać findmnie do podania użytecznej wartości wyjściowej? A przynajmniej żeby uzyskać listę tych plików, których nie znaleziono? (Mogę sobie wyobrazić, że to drugie może być łatwiejsze dzięki sprytnemu wyborowi logicznych połączeń, ale zawsze staram się to rozgryźć.)

Tło / Motywacja: Mam „główną” kopię zapasową i chcę sprawdzić, czy niektóre pliki na mojej lokalnej maszynie istnieją na mojej głównej kopii zapasowej przed ich usunięciem (aby utworzyć trochę miejsca). Zrobiłem więc listę plików, sshedytując je na komputerze głównym, i wtedy nie mogłem znaleźć najlepszego sposobu na znalezienie brakujących plików.

Andrew Stacey
źródło
Zaktualizowałem swoje rozwiązanie, aby korzystać z niego znacznie szybciej locate.
użytkownik nieznany
@ userunknown locatenie pokazuje aktualnego stanu systemu plików, może to być dzień, a nawet tydzień. Jest to odpowiednie jako baza do testowania kopii zapasowych.
Volker Siegel,

Odpowiedzi:

5

finduznaje brak znalezienia za szczególny przypadek sukcesu (nie wystąpił błąd). Ogólnym sposobem sprawdzenia, czy pliki findspełniają określone kryteria, jest sprawdzenie, czy dane wyjściowe findsą puste. Aby uzyskać lepszą wydajność, gdy istnieją pasujące pliki, użyj -quitGNU find, aby sprawić, że zakończy się przy pierwszym dopasowaniu, lub head( head -c 1jeśli jest dostępny, w innym przypadku, head -n 1co jest standardem) w innych systemach, aby umrzeć z powodu pękniętej rury, a nie generować długi wynik.

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

W bash ≥4 lub zsh nie potrzebujesz zewnętrznego findpolecenia do prostego dopasowania nazwy: możesz użyć **/$name. Wersja Bash:

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

Wersja Zsh na podobnej zasadzie:

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

Lub tutaj jest krótszy, ale bardziej tajemniczy sposób testowania istnienia pliku pasującego do wzorca. Kwalifikator glob Npowoduje, że dane wyjściowe są puste, jeśli nie ma dopasowania, [1]zachowuje tylko pierwsze dopasowanie i e:REPLY=true:zmienia każde dopasowanie, aby rozwinąć do 1zamiast nazwy dopasowanego pliku. Tak **/"$name"(Ne:REPLY=true:[1]) falserozwija się true false, jeśli jest mecz, albo po prostu false, jeśli nie ma odpowiednika.

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

Bardziej efektywne byłoby połączenie wszystkich nazwisk w jednym wyszukiwaniu. Jeśli liczba wzorców nie jest zbyt duża dla limitu długości twojego systemu w linii poleceń, możesz połączyć wszystkie nazwy za pomocą -o, wykonać pojedyncze findwywołanie i przetworzyć dane wyjściowe. Jeśli żadna z nazw nie zawiera metaznaków powłoki (tak więc nazwy są również findwzorami), oto sposób na przetworzenie z awk (nieprzetestowane):

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

Innym podejściem byłoby użycie Perla i File::Find, co ułatwia uruchomienie kodu Perla dla wszystkich plików w katalogu.

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

Alternatywnym podejściem jest wygenerowanie listy nazw plików po obu stronach i praca nad porównaniem tekstu. Wersja Zsh:

comm -23 <(<file_list sort) <(print -rl -- **/*(:t) | sort)
Gilles „SO- przestań być zły”
źródło
Akceptuję ten z dwóch powodów. Podoba mi się zshrozwiązanie ze **składnią. Jest to bardzo proste rozwiązanie i chociaż może nie być najbardziej wydajne z punktu widzenia maszyny , jest prawdopodobnie najbardziej wydajne z mojego punktu widzenia! Również pierwsze rozwiązanie tutaj odpowiada na rzeczywiste pytanie , ponieważ skręca się findw coś, w czym kod wyjścia odróżnia „Mam dopasowanie” od „Nie dostałem dopasowania”.
Andrew Stacey,
9

Można użyć statdo ustalenia, czy plik istnieje w systemie plików.

Powinieneś użyć wbudowanych funkcji powłoki, aby sprawdzić, czy istnieją pliki.

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

„Test” jest opcjonalny i skrypt faktycznie będzie bez niego działał, ale zostawiłem go dla czytelności.

Edycja: Jeśli naprawdę nie masz innej opcji, jak pracować nad listą nazw plików bez ścieżek, sugeruję, abyś zbudował listę plików raz za pomocą funkcji find, a następnie iteruj ją za pomocą grep, aby dowiedzieć się, które pliki tam są.

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

Uwaga:

  • lista plików zawiera tylko pliki, a nie katalogi,
  • ukośnik we wzorcu dopasowania grep jest taki, że porównujemy pełne nazwy plików, a nie częściowe,
  • a ostatnie „$” we wzorcu wyszukiwania jest zgodne z końcem wiersza, aby nie uzyskać dopasowania do katalogu, a jedynie pełne łaty nazw plików.
Caleb
źródło
stat potrzebuje dokładnej lokalizacji, prawda? Używam find, ponieważ mam tylko listę nazw plików, które mogą znajdować się w wielu katalogach. Przepraszam, jeśli to nie było jasne.
Andrew Stacey,
Hmmm. Nie powiedziałeś, że masz nazwy plików bez ścieżek! Może zamiast tego możesz naprawić TEN problem? Byłoby to o wiele bardziej wydajne niż szukanie wielu razy w tym samym zestawie danych.
Caleb
Dzięki za edycję i jeszcze raz przepraszam, że nie jestem konkretny. Nazwa / ścieżka pliku nie jest czymś, co zamierzam naprawić - pliki mogą znajdować się w różnych miejscach w dwóch systemach, dlatego chcę mieć rozwiązanie wystarczająco solidne, aby obejść ten problem. Komputer powinien działać zgodnie z moimi specyfikacjami, a nie na odwrót! Poważnie, nie jest to coś, co często robię - szukałem starych plików do usunięcia, aby zrobić miejsce i chciałem tylko „szybko i brudnie” zapewnić, że będą one w moich kopiach zapasowych.
Andrew Stacey,
Przede wszystkim nie potrzebujesz pełnej ścieżki, tylko względnej ścieżki do dowolnej struktury katalogów, której kopię zapasową tworzysz. Pozwól, że zasugeruję, że jeśli ścieżka nie jest taka sama, istnieje duża szansa, że ​​plik nie jest taki sam i możesz uzyskać fałszywie pozytywne wyniki z testu. Wygląda na to, że twoje rozwiązanie może być bardziej brudne niż szybkie; Nie chciałbym widzieć, jak się palisz, myśląc, że masz coś, czego nie miałeś. Ponadto, jeśli pliki są wystarczająco cenne do wykonania kopii zapasowej, nie powinieneś usuwać plików podstawowych, w przeciwnym razie musisz wykonać kopię zapasową!
Caleb
Ak! Pominąłem mnóstwo szczegółów, aby spróbować skoncentrować się na pytaniu, a ty wypełniasz je mnóstwem założeń, które - powiedziałbym - są całkowicie rozsądne, ale są całkowicie błędne! Wystarczy powiedzieć, że wiem, że jeśli plik istnieje i znajduje się w katalogu o określonym typie nazwy, to wiem, że jest to oryginalny plik i można bezpiecznie usunąć kopię na moim komputerze.
Andrew Stacey,
1

Pierwszym uproszczonym podejściem może być:

a) posortuj listę plików:

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

znaleźć braki lub

comm sorted.lst found.lst

znaleźć mecze

  • Pułapki:
    • Nowe linie w nazwach plików są bardzo trudne w obsłudze
    • spacje i podobne rzeczy w nazwach plików też nie są miłe. Ale ponieważ masz kontrolę nad plikami na liście plików, być może to rozwiązanie jest już wystarczające, jednak ...
  • Wady:

    • Gdy find znajdzie plik, nadal działa, aby znaleźć inny i jeszcze jeden. Byłoby miło pominąć dalsze wyszukiwanie.
    • find może wyszukać wiele plików jednocześnie, przy pewnym przygotowaniu:

      znajdź -nazwa a.plik -lub -nazwa -b.plik -lub -nazwa c.plik ...

Czy lokalizacja może być opcją? Ponownie przyjęto wstępnie przygotowaną listę plików:

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

Wyszukiwanie foo.bar nie będzie pasować do pliku foo.ba lub oo.bar z konstruktem --regexp (nie należy go mylić za pomocą wyrażenia regularnego bez p).

Możesz określić konkretną bazę danych do zlokalizowania i musisz ją zaktualizować przed rozpoczęciem wyszukiwania, jeśli potrzebujesz najnowszych wyników.

nieznany użytkownik
źródło
1

Myślę, że to też może być przydatne.

Jest to rozwiązanie jednoliniowe, na wypadek gdybyś wybrał „listę” jako prawdziwe pliki, które chcesz zsynchronizować z innym folderem:

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

aby pomóc w czytaniu:

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

ten przykład wyklucza tworzenie kopii zapasowych plików „* ~” i ograniczenia do zwykłego typu pliku „-type f”

Moc Wodnika
źródło
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}

Może?

Cześć71
źródło
0

Dlaczego po prostu nie porównać długości listy zapytań z długością listy wyników?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Holger Brandl
źródło