Come analizzo l'output del comando find quando i nomi dei file contengono spazi?


12

Utilizzando un loop come

for i in `find . -name \*.txt` 

si interromperà se alcuni nomi di file contengono spazi.

Quale tecnica posso usare per evitare questo problema?


1
Si noti che i file possono anche avere nuove righe nel loro nome. Ecco perché c'è find -print0e xargs -0.
Daniel Beck

Risposte:


12

Idealmente non lo fai affatto, perché analizzare correttamente i nomi dei file in uno script di shell è sempre difficile (correggilo per gli spazi, avrai ancora problemi con altri caratteri incorporati, in particolare newline). Questo è anche elencato come la prima voce nella pagina BashPitfalls.

Detto questo, c'è un modo per fare quasi quello che vuoi:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Ricorda di citare anche $iquando lo usi, per evitare che altre cose interpretino gli spazi in un secondo momento. Ricorda anche di $IFStornare indietro dopo averlo usato, perché non farlo causerà errori sconcertanti in seguito.

Questo ha un altro avvertimento collegato: ciò che accade all'interno del whileloop può avvenire in una subshell, a seconda della shell esatta che stai usando, quindi le impostazioni variabili potrebbero non persistere. La forversione loop lo evita, ma al prezzo che, anche se si applica la $IFSsoluzione per evitare problemi con gli spazi, ci si troverà nei guai se findrestituisce troppi file.

Ad un certo punto la correzione corretta per tutto questo diventa farlo in un linguaggio come Perl o Python invece di shell.


1
Mi piace l'idea di usare Python per evitare tutto questo.
Scott C Wilson,

12

Usalo find -print0e pipe a xargs -0, oppure scrivi il tuo piccolo programma C e pipe al tuo piccolo programma C. Questo è ciò -print0per cui -0sono stati inventati.

Gli script di shell non sono il modo migliore per gestire i nomi di file con spazi: puoi farlo, ma diventa goffo.


Funziona sulla mia macchina ^ TM!
mcandre,

2

È possibile impostare il "separatore di campo interno" ( IFS) su qualcosa di diverso dallo spazio per la suddivisione dell'argomento loop, ad es

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

Ho ripristinato IFSdopo il suo utilizzo in find, soprattutto perché sembra bello, penso. Non ho riscontrato alcun problema nel impostarlo su Newline, ma penso che sia "più pulito".

Un altro metodo, a seconda di ciò che si desidera fare con l'output da find, è utilizzare direttamente -execcon il findcomando oppure utilizzarlo -print0e reindirizzarlo xargs -0. Nel primo caso findsi occupa della fuga del nome del file. Nel -print0caso, findstampa il suo output con un separatore null, quindi si xargsdivide su questo. Poiché nessun nome di file può contenere quel personaggio (quello che so di), anche questo è sempre sicuro. Questo è utile soprattutto in casi semplici; e di solito non è un ottimo sostituto per un forciclo completo .


1

Usando find -print0conxargs -0

L'uso find -print0combinato con xargs -0è completamente affidabile rispetto ai nomi di file legali ed è uno dei metodi più estensibili disponibili. Ad esempio, supponiamo che tu voglia un elenco di tutti i file PDF nella directory corrente. Potresti scrivere

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Questo troverà tutti i PDF (via -iname '*.pdf') nella directory corrente ( .) e qualsiasi sottodirectory e li passerà come argomento al echocomando. Poiché abbiamo specificato l' -n 1opzione, xargspasserà solo un argomento alla volta a echo. Se avessimo omesso quell'opzione, xargsne avremmo passato il maggior numero possibile echo. (Puoi echo short input | xargs --show-limitsvedere quanti byte sono ammessi in una riga di comando.)

Cosa fa xargsesattamente?

Possiamo vedere chiaramente l'effetto che xargsha sul suo input - e l'effetto di -nin particolare - usando uno script che fa eco ai suoi argomenti in un modo più preciso di echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Si noti che gestisce perfettamente spazi e newline,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

che sarebbe particolarmente problematico con la seguente soluzione comune:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Appunti

1

Non sono d'accordo con i bashbasher, perché bash, insieme al set di strumenti * nix, è abbastanza abile nella gestione dei file (compresi quelli i cui nomi hanno spazi bianchi incorporati).

In realtà, findti dà un controllo accurato sulla scelta dei file da elaborare ... Dal lato bash, devi solo capire che devi creare le tue stringhe bash words; tipicamente usando "virgolette doppie", o qualche altro meccanismo come usare IFS o find's{}

Si noti che nella maggior parte / molte situazioni non è necessario impostare e ripristinare IFS; basta usare IFS localmente come mostrato negli esempi seguenti. Tutti e tre gli spazi bianchi gestiscono bene. Inoltre non è necessaria una struttura di loop "standard", perché find's \; è effettivamente un loop; basta inserire la logica del loop in una funzione bash (se non si chiama uno strumento standard).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

E, altri due esempi

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'trova also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)


1
C'è una certa validità per entrambe le prospettive. Quando lavoravo solo sui miei file, usavo semplicemente find e non mi preoccupavo, perché i miei file non hanno spazi (o ritorni a capo!) Nei loro nomi. Ma quando inizi a lavorare con i file di altre persone, devi usare tecniche più robuste.
Scott C Wilson,
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.