Come trovo quali file mancano in un elenco?


9

Ho un elenco di file che voglio verificare se esistono sul mio filesystem. Ho pensato di farlo usando findcome in:

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

(usando zsh) ma non funziona come findsembra uscire 0dal trovare o meno il file. Immagino di poterlo superare attraverso qualche altro test che verifica se findproduce qualche risultato (grezzo ma efficace sarebbe sostituire il > /dev/nullcon |grep '') ma questo sembra come usare un troll per catturare una capra (altre nazionalità potrebbero dire qualcosa su mazze e noci ).

C'è un modo per costringermi finda darmi un utile valore di uscita? O almeno per ottenere un elenco di quei file che non sono stati trovati? (Posso immaginare che quest'ultimo sia forse più facile da un'astuta scelta di connettivi logici, ma mi sembra di essere sempre legato a nodi quando provo a capirlo.)

Sfondo / motivazione: ho un backup "principale" e voglio verificare che alcuni file sul mio computer locale esistano sul mio backup principale prima di eliminarli (per creare un po 'di spazio). Così ho fatto un elenco dei file, sshli ho editati sulla macchina principale, e quindi non sono riuscito a capire il modo migliore per trovare i file mancanti.


Ho aggiornato la mia soluzione per utilizzare molto più velocemente locate.
utente sconosciuto

@userunknown locatenon mostra lo stato corrente del filesystem, potrebbe essere di un giorno o addirittura di una settimana. È adatto come base per testare i backup.
Volker Siegel,

Risposte:


5

findconsidera di non trovare nulla un caso speciale di successo (non si è verificato alcun errore). Un modo generale per verificare se i file soddisfano alcuni findcriteri è verificare se l'output di findè vuoto. Per una migliore efficienza quando ci sono file corrispondenti, usa -quitGNU find per farlo uscire alla prima corrispondenza, oppure head( head -c 1se disponibile, altrimenti head -n 1è standard) su altri sistemi per farlo morire di un tubo rotto piuttosto che produrre un output lungo.

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

In bash ≥4 o zsh, non è necessario il findcomando esterno per una semplice corrispondenza di nomi: è possibile utilizzare **/$name. Versione Bash:

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

Versione Zsh su un principio simile:

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

Oppure ecco un modo più breve ma più criptico di testare l'esistenza di un file corrispondente a un modello. Il qualificatore glob Nrende vuoto l'output se non vi sono corrispondenze, [1]mantiene solo la prima corrispondenza e e:REPLY=true:modifica ogni corrispondenza per espanderla 1anziché il nome del file corrispondente. Quindi si **/"$name"(Ne:REPLY=true:[1]) falseespande in true falsecaso di corrispondenza o solo in falseassenza di corrispondenza.

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

Sarebbe più efficiente combinare tutti i tuoi nomi in una ricerca. Se il numero di motivi non è troppo elevato per il limite di lunghezza del sistema su una riga di comando, è possibile unire tutti i nomi -o, effettuare una singola findchiamata e post-elaborare l'output. Se nessuno dei nomi contiene metacaratteri di shell (quindi anche i nomi sono findpattern), ecco un modo per post-processare con awk (non testato):

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

Un altro approccio sarebbe quello di utilizzare Perl e File::Find, il che semplifica l'esecuzione del codice Perl per tutti i file in una directory.

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

Un approccio alternativo è generare un elenco di nomi di file su entrambi i lati e lavorare su un confronto testuale. Versione Zsh:

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

Accetto questo per due motivi. Mi piace la zshsoluzione con la **sintassi. È una soluzione molto semplice e sebbene possa non essere la più efficiente in termini di macchina , è probabilmente la più efficiente in termini di me che la ricordo davvero! Inoltre, la prima soluzione qui risponde alla domanda reale in quanto si findtrasforma in qualcosa in cui il codice di uscita distingue "Ho ottenuto una corrispondenza" da "Non ho ottenuto una corrispondenza".
Andrew Stacey,

9

È possibile utilizzare statper determinare se esiste un file nel file system.

È necessario utilizzare le funzioni di shell integrate per verificare se esistono file.

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

Il "test" è facoltativo e lo script funzionerà effettivamente senza di esso, ma l'ho lasciato lì per leggibilità.

Modifica: se davvero non hai altra scelta che lavorare per un elenco di nomi di file senza percorsi, ti suggerisco di creare un elenco di file una volta con find, quindi iterare su di esso con grep per capire quali file ci sono.

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

Nota che:

  • l'elenco dei file include solo file non directory,
  • la barra nel modello di corrispondenza grep è quindi confrontiamo i nomi di file completi non parziali,
  • e l'ultimo '$' nel modello di ricerca deve corrispondere alla fine della riga in modo da non ottenere corrispondenze di directory, ma solo patch di nome file completo.

stat ha bisogno della posizione esatta, no? Sto usando find perché ho solo un elenco di nomi di file e potrebbero trovarsi in numerose directory. Scusa se non era chiaro.
Andrew Stacey,

Hmmm. Ya non hai detto di avere nomi di file senza percorsi! Forse puoi risolvere quel problema invece? Sarebbe molto più efficiente che trovare un sacco di volte nello stesso set di dati.
Caleb,

Grazie per la modifica e scusami ancora per non essere specifico. Il nome / percorso del file non è qualcosa che risolverò: i file potrebbero trovarsi in posizioni diverse sui due sistemi, quindi desidero una soluzione abbastanza solida da aggirare il problema. Il computer dovrebbe funzionare secondo le mie specifiche, non viceversa! Scherzi a parte, questo non è qualcosa che faccio spesso - stavo cercando alcuni vecchi file da eliminare per fare spazio e volevo solo un modo "rapido e sporco" per assicurarmi che fossero nei miei backup.
Andrew Stacey,

Innanzitutto non è necessario il percorso completo, ma solo un percorso relativo a qualsiasi struttura di directory di cui si sta eseguendo il backup. Consentimi di suggerire che se il percorso non è lo stesso, ci sono buone probabilità che il file non sia lo stesso e potresti ottenere falsi positivi dal tuo test. Sembra che la tua soluzione potrebbe essere più sporca che veloce; Non vorrei vederti bruciato pensando di avere qualcosa che non hai. Inoltre, se i file sono abbastanza preziosi per il backup in primo luogo, non è necessario eliminare le primarie, altrimenti è necessario eseguire il backup dei backup!
Caleb,

Ak! Ho lasciato fuori un sacco di dettagli per cercare di focalizzare la domanda e tu li stai riempiendo con un carico di ipotesi che - dovrei dire - sono perfettamente ragionevoli ma capita di essere completamente sbagliato! Basti dire che so che se il file è lì ed è in una directory con un particolare tipo di nome, allora so che è il file originale ed è sicuro eliminare la copia sulla mia macchina.
Andrew Stacey,

1

Un primo approccio semplicistico potrebbe essere:

a) ordina la tua lista file:

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

per trovare mancanze, o

comm sorted.lst found.lst

per trovare le partite

  • insidie:
    • Le newline nei nomi dei file sono molto difficili da gestire
    • spazi vuoti e cose simili nei nomi dei file non sono altrettanto belli. Ma dal momento che hai il controllo sui file nell'elenco dei file, forse questa soluzione è già sufficiente, tuttavia ...
  • svantaggi:

    • Quando find trova un file, continua a funzionare per trovarne un altro e un altro. Sarebbe bello saltare ulteriori ricerche.
    • find potrebbe cercare più file contemporaneamente, con un po 'di preparazione:

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

Potrebbe essere un'opzione? Ancora una volta, si presumeva un elenco predefinito di file:

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

Una ricerca di foo.bar non corrisponderà a un file foo.ba, o oo.bar con il costrutto --regexp (da non confondere con regex senza p).

È possibile specificare un database specifico per individuare e è necessario aggiornarlo prima della ricerca, se sono necessari i risultati più recenti.


1

Penso che anche questo possa essere utile.

Questa è una soluzione a una riga, nel caso in cui opti per il tuo "elenco" sii file reali che desideri sincronizzare con un'altra cartella:

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

per aiutare a leggere:

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

questo esempio esclude i file di backup "* ~" e limita il normale tipo di file "-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}

Può essere?


0

Perché non semplicemente confrontare la lunghezza dell'elenco delle query con la lunghezza dell'elenco dei risultati?

while read p; do
  find . -name $p 2>/dev/null
done < file_list.txt | wc -l
wc -l file_list.txt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.