Problema con spazi nei nomi dei file


8

Voglio fare qualcosa ripetutamente su un elenco di file. I file nelle domande hanno spazi nei loro nomi:

david@david: ls -l
total 32
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha
-rw-rw-r-- 1 david david  0 Mai  8 11:55 haha~
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (3rd copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (4th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (5th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (6th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (7th copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (another copy)
-rw-rw-r-- 1 david david 13 Mai  8 11:55 haha (copy)

Ora voglio stat ciascuno di questi file:

david@david: echo '
for file in $(ls)
do
stat $file
done' | bash

(Uso echo e una pipe per scrivere comandi multilinea.)

Quando lo faccio, funziona correttamente su quei file che non hanno spazi nei loro nomi. Ma gli altri ...

stat: cannot stat ‘(another’: No such file or directory
stat: cannot stat ‘copy)’: No such file or directory

Il passaggio $(ls)a "$(ls)"o $filesu "$file"non funziona. Cosa posso fare?

Modificare:

echo '
for files in *
do
stat "$files"
done' | bash

fa il trucco! Dato che sono nuovo a bash, voglio mantenere le cose il più semplice possibile, quindi niente con il tentativo di sfuggire agli spazi, o usando xargso la soluzione con read -r, sebbene risolvano il problema.

Come alcuni hanno chiesto: Sì, usare questo invece di stat *è strano. Ma volevo solo trovare un modo generale per applicare lo stesso comando su un mucchio di nomi di file in bash, usando un ciclo for. Quindi statpotrebbe rappresentare gzip, gpgo rm.


1
cosa c'è che non va stat *? (;-)
Rmano,


Uso solo stat come esempio. :) Voglio raccogliere i nomi dei file con ls, quindi utilizzare i risultati di ls in un ciclo bash.
user258532

Risposte:


12

La citazione multipla dal echo 'sta complicando la cosa.

Puoi semplicemente usare:

for f in *; do stat -- "$f"; done

Ma anche

stat -- * 

... e se vuoi raccogliere i file e quindi applicare il comando (perché?) puoi andare con (ma stai attento con il file contenente nuove righe ... (1))

for f in *; do echo "$f"; done | xargs stat --

... e se vuoi anche i file nascosti, basta usare * .*come modello, ma poi ricordalo .e ..sarà nel set .

A parte questo, non dovresti analizzare l' lsoutput .


(1) ma se hai nomi di file con newline, te lo meriti in qualche modo ... ;-)


"e quindi applicare il comando (perché?)" -> stat serve solo come comando arbitrario, mentre sto provando come eseguire loop bash con nomi di file. Potrebbe essere gpg, gzip o qualsiasi altra cosa.
user258532

@ user258532 qualunque sia il comando, usa sempre for f in *; do command "$f"; done. Non analizzare mai ls, certamente mai farlo in un for ciclo e perché usarlo echo?
terdon,

echo: Perché mi piace scrivere i comandi su più righe ... :)
user258532

2
@ user258532 Huh? Perché dovresti aver bisogno di eco per quello? Basta premere invio e continuare su una nuova riga. Se si finisce una linea con un preventivo aperta o su una delle do, |, &&ecc, si può proseguire sulla nuova linea. O quello o usa heredocs. Nessun motivo per l'uso echoe può anche causare problemi.
terdon,

"Basta premere invio e continuare su una nuova riga." Ahia. MrGreen Ora il mio QI è ufficialmente inferiore a 0 - sai, sono davvero nuovo a bash e cose del genere. Ma in realtà guadagno i miei soldi con R (la suite statistica e il linguaggio di scripting).
user258532

6

Nota a margine: è possibile dividere comandi lunghi / complicati su più righe aggiungendo uno spazio seguito da una barra rovesciata e premendo Enterogni volta che si desidera iniziare a scrivere in una nuova riga, anziché forzare più processi utilizzando echo [...] | bash; inoltre dovresti racchiuderlo $filetra virgolette doppie, per evitare la statrottura in caso di nomi di file contenenti spazi:

for file in $(ls); \
do \
stat "$file"; \
done

Il problema è che si $(ls)espande in un elenco di nomi di file contenenti spazi, e lo stesso accadrà anche con "$(ls)".

Anche risolvendo questo problema, questo metodo si interromperà comunque sui nomi dei file contenenti barre rovesciate e sui nomi dei file contenenti nuove righe (come sottolineato da Terdon).

Una soluzione per entrambi i problemi sarebbe quella di convogliare l'output di findun whileloop in esecuzione in read -rmodo che, ad ogni iterazione, read -rmemorizzerà una riga finddell'output in $file:

find . -maxdepth 1 -type f | while read -r file; do \
    stat "$file"; \
done

1
e file nascosti? :)
AB

5
Ciò non riuscirà comunque nei nomi di file contenenti newline. Basta non analizzare ls. Mai.
terdon,

4
@ user258532 no non lo è. Seriamente, non analizzarels . Ci sono modi migliori e più robusti. Potresti anche voler leggere questo: Perché * non * analizzare `ls`? per ulteriori dettagli.
terdon,

1
Il problema qui non è ls, è for- scorre le parolefor (separate da IFS) fornite dopo la parola chiave`in
glenn jackman

1
@glennjackman bene sì, è la combinazione di fore ls. for f in *andrebbe bene, per esempio.
terdon,

3

Usa il buon vecchio find, funziona con file nascosti, newline e spazi.

find . -print0 | xargs -I {} -0 stat {}

o qualsiasi altro invece di stat

find . -print0 | xargs -I {} -0 file {}
find . -print0 | xargs -I {} -0 cat {}

1

Come ragazzo R, ho già trovato una soluzione alternativa in R:

filenames <- dir(); # reads file names into an array.
                    # It works also recursively
                    # with dir(recursive=TRUE)
for (i in 1:length(filenames)) {
system(     # calls a system function
 paste(     # join stat and the file name
  "stat",
  filenames[i]
 )
)
}

Lo so, è pazzo. Vorrei che l'output di lsfosse più facile da analizzare ... R può gestire gli spazi, perché dir () restituisce un valore di carattere tra virgolette. Qualunque cosa tra le virgolette è quindi un nome file valido con spazi.


3
Non preoccuparti (ma +1 per lo sforzo! :). Basta usare for f in *, premere invio e continuare su una nuova riga do:, premere di nuovo stat "$f"invio, entrare di nuovo, doneentrare. Questo è un comando suddiviso piacevolmente su 4 righe e non si interromperà su alcun tipo di nome file.
terdon,

1

Mi sono imbattuto in altri casi di problemi relativi agli spazi bianchi per i loop, quindi il seguente comando (imo più robusto) è quello che uso generalmente. Si adatta perfettamente anche ai tubi.

$ ls | while read line; do stat "$line"; done;

È possibile combinare questo con grepo utilizzare findinvece:

$ find ./ -maxdepth 2 | grep '^\./[/a-z]+$' | while read line; do stat "$line"; done;

La tua prima risposta non riesce sui nomi di file che contengono barre rovesciate o spazi iniziali o finali. La tua seconda risposta fallisce totalmente se non aggiungi l' -Eopzione a grep, senza la quale non riconoscerà +in un'espressione regolare. Anche allora, è un'oscillazione e una mancanza su questa domanda, poiché grep rimuovi i nomi di file contenenti spazi . Rimuove anche i nomi di file contenenti cifre (numeri) e punteggiatura (ad esempio, parentesi), come fanno gli esempi nella domanda. E questo non menziona nemmeno i nomi di file che contengono newline o iniziano con -(trattino).
Scott,

-1

Questa risposta risolverà il problema dell'analisi lse prenderà cura dei backspaces e delle nuove linee

Prova questo, risolverà il tuo problema usando il separatore di campo interno IFS.

IFS="\n" for f in $(ls); do   stat "$f"; done

Ma puoi anche risolverlo facilmente, senza bisogno di analizzare l'output usando

for f in *; do   stat "$f"; done

1
non funziona per i file nascosti.
AB

2
OP non ha richiesto file nascosti
Maythux,

1
Non vedo perché è necessario modificare IFSqui: la citazione della variabile dovrebbe essere sufficiente per evitare la divisione delle parole, sicuramente?
steeldriver,

per l'analisi ls .
Maythux,

Per quello che è il downvote !!!
Maythux,

-1

Invece, puoi rinominare i tuoi file sostituendo lo spazio con qualche altro carattere come il trattino basso, in modo da sbarazzarti di questo problema:

Per farlo, esegui facilmente il comando:

for file in * ; do mv "$f" "${f// /_}" ; done
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.