Come posso usare find quando il nome file contiene spazi?


17

Voglio reindirizzare i nomi dei file ad altri programmi, ma tutti si strozzano quando i nomi contengono spazi.

Diciamo che ho un file chiamato.

foo bar

Come posso ottenere findper restituire il nome corretto?

Ovviamente voglio:

foo\ bar

o:

"foo bar"

EDIT : Non voglio passare attraverso xargs, voglio ottenere una stringa correttamente formattata in findmodo da poter reindirizzare la stringa di nomi di file direttamente a un altro programma.


5
a cosa lo stai collegando? sei a conoscenza della -execbandiera con find? potresti potenzialmente alleviare questo errore e rendere il tuo comando più efficiente eseguendo -execinvece il piping ad altri comandi. Solo il mio $ 0,02
h3rrmiller

6
@bug: findformatta bene i nomi dei file; sono scritti un nome per riga. (Naturalmente, questo è ambiguo se un nome file contiene un carattere di nuova riga.) Quindi il problema è che la parte ricevente "soffoca" quando ottiene uno spazio, il che significa che devi dirci qual è la parte ricevente se vuoi una risposta significativa .
rici,

2
Quello che chiami "formattato correttamente" è in realtà "evitato dal consumo dalla shell". La maggior parte dei programmi di utilità in grado di leggere un sacco di nomi di file soffocerebbe su un nome con escape dalla shell, ma in effetti avrebbe senso (diciamo) findoffrire un'opzione per produrre nomi di file in un formato adatto alla shell. In generale, tuttavia, l' estensione -print0GNU findfunziona bene anche per molti altri scenari e dovresti imparare ad usarlo in ogni caso.
Tripleee

2
@bug: A proposito, ls $(command...)non sfoglia l'elenco stdin. Mette l'output di $(command...)direttamente nella riga di comando. In tal caso, è la shell che sta leggendo da c, e utilizzerà il valore corrente di $IFSper decidere come dividere le parole dell'output. In generale, stai meglio usando xargs. Non noterai un hit da prestazione.
rici,

2
find -printf '"%p"\n'aggiungerà virgolette doppie intorno a ciascun nome trovato, ma non citerà correttamente le doppie virgolette in un nome file. Se i nomi dei tuoi file non hanno virgolette doppie incorporate, puoi ignorare il problema: oppure esegui il pipe through sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Se i nomi dei tuoi file finiscono per essere gestiti dalla shell, dovresti probabilmente usare virgolette singole invece che doppie (altrimenti sweet$HOMEdiventerà qualcosa di simile sheet/home/you). E questo non è ancora molto robusto rispetto ai nomi dei file con nuove righe al loro interno. Come vuoi gestirli?
Tripleee

Risposte:


18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

Con findsupporti -print0e xargssupporti -0:

find . -type f -print0 | xargs -0 <command>

-0 L'opzione dice a xargs di usare il carattere ASCII NUL invece di spazio per terminare (separare) i nomi dei file.

Esempio:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l

Non funziona Quando eseguo ls $(find . -maxdepth 1 -type f -print0 | xargs -0)ottengo ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
bug

1
L'hai provato nel modo in cui Gnouc l'ha effettivamente scritto? Se insisti nel farlo a modo tuo, prova a racchiudere $(..)tra virgolette doppie"$(..)"
evilsoup

3
@bug: il tuo comando è sbagliato. Prova esattamente I worte e leggi la manpage di finde xargs.
cuonglm

Vedo, quindi di nuovo voglio ottenere una stringa formattata che potrei reindirizzare direttamente.
bug

1
@bug: basta usare xargs -0 <il tuo programma>
cuonglm

10

L'uso -print0è un'opzione, ma non tutti i programmi supportano l'utilizzo di flussi di dati delimitati da nullbyte, quindi dovrai usare xargsl' -0opzione per alcune cose, come notato dalla risposta di Gnouc.

Un'alternativa sarebbe quella di usare findle opzioni -execo -execdir. Il primo dei seguenti fornisce i nomi dei file somecommanduno alla volta, mentre il secondo si espande in un elenco di file:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Potresti scoprire che stai meglio usando il globbing in molti casi. Se hai una shell moderna (bash 4+, zsh, ksh), puoi ottenere ricorsive globbing con globstar( **). In bash, devi impostare questo:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

Ho una riga che dice shopt -s globstar extglobnel mio .bashrc, quindi questo è sempre abilitato per me (e quindi sono globi estesi, che sono anche utili).

Se non vuoi ricorsività, ovviamente usa ./*.txtinvece invece, per usare ogni * .txt nella directory di lavoro. findha alcune utilissime funzionalità di ricerca a grana fine ed è obbligatorio per decine di migliaia di file (a quel punto ti imbatterai nel numero massimo di argomenti della shell), ma per l'uso quotidiano spesso non è necessario.


Ehi, @evilsoup, che cosa fa {} in questo script?
Ayusman,

3

Personalmente, userei l' -execazione find per risolvere questo tipo di problema. O, se necessario, xargsche consente l'esecuzione parallela.

Tuttavia, esiste un modo per findprodurre un elenco di nomi di file leggibili da bash. Non sorprende che utilizzi -exece bash, in particolare, un'estensione del printfcomando:

find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'

Tuttavia, sebbene ciò stamperà correttamente le parole con escape dalla shell, non sarà utilizzabile con $(...), perché $(...)non interpreta virgolette o escape. (Il resut di $(...)è soggetto alla suddivisione delle parole e all'espansione del nome del percorso, a meno che non sia racchiuso tra virgolette.) Quindi quanto segue non farà ciò che vuoi:

ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)

Quello che dovresti fare è:

eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"

(Nota che non ho fatto alcun tentativo reale per testare la mostruosità sopra.)

Ma allora potresti anche fare:

find ... -exec ls {} +

Non credo che lo lsscenario catturi adeguatamente il caso d'uso del PO, ma questa è solo una speculazione, dal momento che non ci è stato mostrato ciò che sta effettivamente cercando di realizzare. Questa soluzione funziona davvero molto bene; Ottengo l'output che (vagamente) mi aspettavo per tutti i nomi di file divertenti che ho provato, inclusotouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee

@triplee: non ho idea neanche di cosa voglia fare OP. L'unico vero vantaggio di costruire la stringa tra virgolette a cui passare evalè che non devi ancora passarla eval; potresti salvarlo in un parametro e usarlo in seguito, forse più volte con comandi diversi. Tuttavia, OP non fornisce alcuna indicazione che questo è il caso d'uso (e, se lo fosse, potrebbe essere meglio mettere i nomi dei file in un array, anche se questo è anche complicato.)
rici

0
find ./  | grep " "

ti porterà i file e le directory contiene spazi

find ./ -type f  | grep " " 

ti porterà i file contiene spazi

find ./ -type d | grep " "

ti porterà le directory contiene spazi


-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'

Questa è una risposta interessante, ma non è una risposta a questa domanda.
Scott,
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.