il ciclo attraverso `ls` si traduce in script shell bash


94

Qualcuno ha uno script di shell modello per fare qualcosa con lsun elenco di nomi di directory e scorrere in ciascuno di essi e fare qualcosa?

Sto programmando di fare ls -1d */per ottenere l'elenco dei nomi delle directory.

Risposte:


111

Modificato per non usare ls dove farebbe un glob, come suggerito da @ shawn-j-goff e altri.

Basta usare un for..do..doneloop:

for f in *; do
  echo "File -> $f"
done

È possibile sostituire il *con *.txto qualsiasi altro glob che restituisce un elenco (di file, directory o qualsiasi altra cosa), un comando che genera un elenco, ad esempio $(cat filelist.txt), o sostituirlo effettivamente con un elenco.

All'interno del dociclo, fai semplicemente riferimento alla variabile del ciclo con il prefisso del simbolo del dollaro (quindi $fnell'esempio sopra). Puoi echofarlo o fare qualsiasi altra cosa tu voglia.

Ad esempio, per rinominare tutti i .xmlfile nella directory corrente in .txt:

for x in *.xml; do 
  t=$(echo $x | sed 's/\.xml$/.txt/'); 
  mv $x $t && echo "moved $x -> $t"
done

O ancora meglio, se stai usando Bash puoi usare le espansioni dei parametri di Bash invece di generare una subshell:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done

22
Cosa succede se il nome file contiene uno spazio?
Daniel A. White,

4
Sfortunatamente, come ha detto Daniel, il codice in questa risposta si interromperà se uno dei file o delle cartelle contiene uno spazio o una nuova riga nel loro nome. Mostra un uso improprio molto comune dei forloop e una trappola tipica del tentativo di analizzare l'output di ls. @ DanielA.Bianco, potresti considerare di non accettare questa risposta se non è stata utile (o è potenzialmente fuorviante), dal momento che come hai detto, stai agendo su directory. La risposta di Shawn J. Goff dovrebbe fornire una soluzione più solida e funzionante al tuo problema.
Slhck,

4
-∞ Non dovresti analizzare l' lsoutput , non dovresti leggere l'output in un forciclo , dovresti usare $()invece di `` and Use More Quotes ™ . Sono sicuro che @CoverosGene intendesse bene, ma è terribile.
l0b0,

1
Un'alternativa migliore se vuoi davvero usarla lsè ls -1 | while read line; do stuff; done. Almeno quello non si romperà per gli spazi bianchi.
ejoubaud,

1
con mv -vte non serveecho "moved $x -> $t"
DmitrySandalov il

69

Usare l'output di lsper ottenere nomi di file è una cattiva idea . Può portare a malfunzionamenti e persino a script pericolosi. Questo perché un nome di file può contenere qualsiasi carattere tranne /ed il nullcarattere, e lsnon utilizzare uno dei quei caratteri come delimitatori, quindi se un nome di file ha uno spazio o un ritorno a capo, si sarà ottenere risultati inaspettati.

Esistono due modi molto efficaci per scorrere i file. Qui, ho usato semplicemente echoper dimostrare di fare qualcosa con il nome file; puoi usare qualsiasi cosa, però.

Il primo consiste nell'utilizzare le funzionalità di globbing native della shell.

for dir in */; do
  echo "$dir"
done

La shell si espande */in argomenti separati che il forciclo legge; anche se c'è uno spazio, una nuova riga o qualsiasi altro carattere strano nel nome del file, forvedrà ogni nome completo come un'unità atomica; non sta analizzando l'elenco in alcun modo.

Se si vuole andare in modo ricorsivo in sottodirectory, allora questo non farà a meno che il guscio ha alcune caratteristiche globbing estese (ad esempio bash's globstar. Se la shell non ha queste caratteristiche, o se si vuole garantire che lo script funziona su una varietà di sistemi, quindi l'opzione successiva è usare find.

find . -type d -exec echo '{}' \;

Qui, il findcomando chiamerà echoe gli passerà un argomento del nome file. Lo fa una volta per ogni file che trova. Come nell'esempio precedente, non è possibile analizzare un elenco di nomi di file; invece, un fileneame viene inviato completamente come argomento.

La sintassi -execdell'argomento sembra un po 'divertente. findprende il primo argomento dopo -exece lo considera come programma da eseguire, e ogni argomento successivo, serve come argomento per passare a quel programma. Ci sono due argomenti speciali che -execdevono essere visti. Il primo è {}; questo argomento viene sostituito con un nome file findgenerato dalle parti precedenti . Il secondo è ;, che fa findsapere che questa è la fine della lista di argomenti da passare al programma; findne ha bisogno perché puoi continuare con più argomenti che sono previsti finde non destinati al programma exec'd. Il motivo \è che anche la shell tratta;specialmente - rappresenta la fine di un comando, quindi dobbiamo evitarlo in modo che la shell lo dia come argomento findpiuttosto che consumarlo per se stesso; un altro modo per far sì che la shell non la tratti in modo particolare è inserirla tra virgolette: ';'funziona altrettanto bene sia \;per questo scopo.


2
+1 questo è sicuramente il modo di procedere quando è necessario generare un elenco di file e utilizzarlo in un comando. find -exec è limitato dal solo fatto di poter eseguire un singolo comando. Con il loop, puoi convogliare il contenuto del tuo cuore.
MaQleod,

+1. Vorrei che più persone usassero find. C'è della magia che può essere fatta e persino i limiti percepiti di -execpossono essere aggirati. L' -print0opzione è anche utile per l'uso con xargs.
ghoti,

L'opzione loop non mostrerà le directory nascoste. L'opzione di ricerca non mostra link simbolici.
jinawee,

16

Per i file con spazi all'interno devi assicurarti di citare la variabile come:

 for i in $(ls); do echo "$i"; done; 

oppure, è possibile modificare la variabile di ambiente IFS (input field separator):

 IFS=$'\n';for file in $(ls); do echo $i; done

Infine, a seconda di cosa stai facendo, potresti non aver nemmeno bisogno di ls:

 for i in *; do echo "$i"; done;

buon uso di una subshell nel primo esempio
Jeremy L,

Perché IFS ha bisogno di un $ dopo l'incarico e prima del nuovo personaggio?
Andy Ibanez,

3
I nomi dei file possono contenere anche nuove righe. La rottura \nè insufficiente. Non è mai una buona idea raccomandare di analizzare l'output di ls.
ghoti,


4

Solo per aggiungere alla risposta di CoverosGene, ecco un modo per elencare solo i nomi delle directory:

for f in */; do
  echo "Directory -> $f"
done

1

Perché non impostare IFS su una nuova riga, quindi acquisire l'output di lsin un array? L'impostazione di IFS su newline dovrebbe risolvere i problemi con caratteri divertenti nei nomi dei file; l'utilizzo lspuò essere utile perché ha una funzione di ordinamento integrata.

(Durante i test ho riscontrato difficoltà nell'impostare IFS \nma l'impostazione su backline di nuova riga funziona, come suggerito altrove qui):

Ad esempio (supponendo che il lsmodello di ricerca desiderato sia passato $1):

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

FILES=($(/bin/ls "$1"))

for AFILE in ${FILES[@]}
do
    ... do something with a file ...
done

IFS=$SAVEIFS

Ciò è particolarmente utile su OS X, ad esempio per acquisire un elenco di file ordinati per data di creazione (dalla più vecchia alla più recente), il lscomando è ls -t -U -r.


2
I nomi dei file possono anche contenere newline e spesso quando gli utenti sono autorizzati a nominare i propri file. La rottura \nè insufficiente. Le uniche soluzioni valide utilizzano un ciclo for con espansione del percorso o find.
ghoti,

L'unico modo affidabile per trasferire un elenco di nomi di file è separarli con un carattere NUL, poiché questo è l'unico che non è assolutamente contenuto in un percorso di file.
glglgl

-3

È così che lo faccio, ma probabilmente ci sono modi più efficienti.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt

6
Attenersi alle pipe al posto del file:>ls | while read i; do echo filename: $i; done
Jeremy L

Freddo. Dovrei dire che puoi anche usare $ EDITOR filelist.txt tra i due comandi. Un sacco di cose che puoi fare in un editor è più facile che dalla riga di comando. Non pertinente a questa domanda, però.
ALBERO

La tua soluzione non risolve affatto il problema con i nomi dei file contenenti newline e altre cose fantasiose.
glglgl
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.