trova | xargs shasum crea il checksum del file checksum stesso (prematuramente) e fallisce durante il controllo


10

Il mio problema (in uno script con #!/bin/sh) è il seguente: provo a fare il checksum di tutti i file in una directory a scopo di archiviazione. Il file checksum (nel mio caso sha1) con tutti i nomi di file dovrebbe risiedere nella stessa directory. Diciamo che abbiamo una directory ~/testcon file f1e f2:.

mkdir ~/test
cd ~/test
echo "hello" > f1
echo "world" > f2

Ora calcolando i checksum con

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum

fa esattamente quello che voglio, elenca solo tutti i file della directory corrente e calcola le somme sha1 (maxdepth può essere modificato in seguito). L'output su STDOUT è:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Sfortunatamente, quando provo a salvarlo in un file con

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum > sums.sha1

il file risultante visualizza il checksum per se stesso:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

e quindi fallisce in un secondo momento shasum --check, a causa dell'ovvio problema di ulteriori modifiche al file durante il salvataggio dell'ultima somma.

Mi sono guardato intorno e usando -pflag per xargsho scoperto che in qualche modo crea il file di output prima ancora di eseguire il comando find, quindi il file aggiuntivo viene trovato e verrà sommato ...

So che, come soluzione alternativa, potrei salvare il checksum in un'altra posizione (directory temporanea tramite mktemp) o escluderlo in find in modo specifico, ma mi piacerebbe capire perché si comporta in questo modo - che ai miei occhi non è così utile, ad esempio se il primo comando controlla se il file di output è già su disco, non otterrà mai la risposta corretta ...


8
Non lo è xargs, è la shell stessa che crea questo file, perché prima che qualsiasi comando venga eseguito la shell reindirizza tutti gli input, output e pipe, in modo che quando findinizia il file di output esiste già. Utilizzare -execinvece:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij

@jimmij, non è garantito che funzioni anche se shsono necessarie più invocazioni. Si noti che è necessario un argomento per $0prima {}.
Stéphane Chazelas,

@jimmij La tua altra risposta suggerita teeè svanita? L'ho provato e funziona benissimo, ho anche eliminato STDOUT con l'aggiunta di 1>/dev/null. C'era qualcosa che non andava nella risposta o era un bug?
user121391,

@ user121391 Stephane ha sottolineato che a volte può esserci un problema di condizioni di gara, ciò che sembra vero. L'ho cancellato per un po 'in modo che tu possa guardare, ma se hai molti file nell'elenco quel comando potrebbe andare storto.
Jimmij,

@jimmij ah, capisco. Potrebbe essere utile se lo hai preceduto da un avviso sui problemi, perché penso che non sia così noto che ciò possa accadere. Altrimenti, avrei accettato la tua risposta per i casi se le esecuzioni ricorrenti includono il vecchio file e quello di Anthon per i casi in cui dovrebbe essere sovrascritto.
user121391

Risposte:


12

È possibile impedire il raggiungimento del file xargsutilizzando:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\n' |
  xargs -r shasum -- > sums.sha1

Per evitare problemi con il nome del file che ha spazi vuoti o nuove righe o virgolette o barre rovesciate, vorrei tuttavia utilizzare:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\0' |
  xargs -r0 shasum -- > sums.sha1

anziché.

Il --è per evitare problemi con i nomi di file che iniziano con -. Tuttavia non aiuterà per un file chiamato -. Se avessi usato -print0invece di -printf '%P\0', non avresti avuto bisogno di --e non avresti avuto problemi con il -file.


La tua soluzione è quella che ho finito per usare. In particolare mi piace il fatto che le esecuzioni successive non modificano il file checksum e gonfiano la directory. Inoltre, nel mio script ho usato basenameper ottenere il nome del file sums.sha1 dal percorso completo indicato (questo non era incluso nella domanda, ma potrebbe aiutare gli altri).
user121391

7

Dal momento che stai usando -maxdepth 1, presumo che tu non voglia ricorsione. In tal caso, basta farlo nella shell:

for f in ~/test/*; do
    shasum -- "$f"
done > sums.sha1

Per saltare le directory, puoi fare:

for f in ~/test/*; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Se hai bisogno di ricorsione e stai usando bash, fai:

shopt -s globstar
for f in ~/test/**; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

Si noti che tutti questi approcci hanno il vantaggio di lavorare su nomi di file arbitrari, inclusi quelli con spazi, newline o altro.


Penso che menzionerai che questo risolve qualsiasi problema che l'OP avrebbe con i nomi dei file con anche nuove righe. D'altra parte, se sums.sha1è già presente (da una corsa precedente), la soluzione lo incorporerà.
Anthon,

Spiacenti, non ho chiarito prima: il maxdepth è stato utilizzato solo in questo esempio, utilizzo una funzione in cui l'utente / script può fornire qualsiasi valore, anche se al momento ho solo bisogno della profondità 1.
user121391

@ user121391 vedere la risposta aggiornata per un approccio ricorsivo.
terdon

Nota che proverà anche a fare il checksum di altri tipi di file non regolari come pipe, dispositivi ... (e collegamenti simbolici ad essi).
Stéphane Chazelas,

Grazie, personalmente sto usando sh, ma la tua risposta potrebbe aiutare gli altri.
user121391

4

con zsh:

shasum -- *(D.) > sums.sha1

Il glob verrà espanso prima che venga effettuato il reindirizzamento, quindi sums.sha1non verrà incluso se non era presente in primo luogo.

Dè includere dot-file (file nascosti) come findfarebbe. .è selezionare solo file regolari (come il tuo -type f).

Per escludere sums.sha1comunque nel caso in cui fosse lì in primo luogo:

setopt extendedglob # best in ~/.zshrc
shasum -- ^sums.sha1(D.) > sums.sha1

Nota che quelli eseguono un comando shasum, quindi potresti finire per vedere un errore "Elenco Arg troppo lungo" se l'elenco è enorme. Per ovviare a questo:

autoload zargs
zargs -e/ -- *(D.) / shasum > sums.sha1

Consiglierei di utilizzare ./*invece di *evitare potenziali problemi con un file chiamato -.


Ho modificato la domanda con il tipo di shell, ma la tua risposta mi ricorda che volevo passare a zsh qualche tempo fa ...;)
user121391

1

Come già indicato nelle altre risposte, il problema è che la shell si apre e crea il sums.sha1file, prima di eseguire la pipeline. È possibile utilizzare il programma spongeche fa parte del moreutilspacchetto di molte distribuzioni. Contrariamente al reindirizzamento della shell spongeattenderà fino a quando non ha ricevuto tutto, prima di aprire il file. Viene generalmente utilizzato quando si desidera scrivere un file letto nella stessa pipeline.

Nel tuo caso viene utilizzato in questo modo:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A

0

In alternativa a find / xargs etc potresti voler sha1deep. Probabilmente si trova in un pacchetto diverso - sulla mia scatola viene fornito nel pacchetto md5deep.

Come altri hanno già detto, sums.sha1 viene creato dalla shell anche prima dell'inizio della ricerca. Un trucco con ! -name sums.sha1to findfunzionerà, così come lo sarà

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
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.