Durchlaufen von ls führt zu einem Bash-Shell-Skript

93

Verfügt jemand über ein Template-Shell-Skript, mit dem er etwas lsfür eine Liste von Verzeichnisnamen tun und die einzelnen durchlaufen und etwas tun kann?

Ich habe vor ls -1d */, die Liste der Verzeichnisnamen abzurufen.

Daniel A. White
quelle

Antworten:

110

Bearbeitet, um nicht ls zu verwenden, wo ein Globus tun würde, wie @ shawn-j-goff und andere vorgeschlagen haben.

Benutze einfach eine for..do..doneSchleife:

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

Sie können das *mit *.txtoder jedes andere Glob ersetzen, das eine Liste (von Dateien, Verzeichnissen oder irgendetwas anderem) zurückgibt, einen Befehl, der beispielsweise eine Liste erzeugt $(cat filelist.txt), oder sie tatsächlich durch eine Liste ersetzen.

Innerhalb der doSchleife verweisen Sie einfach auf die Schleifenvariable mit dem Dollarzeichen-Präfix (so $fim obigen Beispiel). Sie können echoes tun oder was Sie wollen.

Um beispielsweise alle .xmlDateien im aktuellen Verzeichnis umzubenennen, gehen Sie wie folgt vor .txt:

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

Oder noch besser, wenn Sie Bash verwenden, können Sie Bash-Parametererweiterungen verwenden, anstatt eine Subshell zu erzeugen:

for x in *.xml; do 
  t=${x%.xml}.txt
  mv $x $t && echo "moved $x -> $t"
done
CoverosGene
quelle
22
Was ist, wenn der Dateiname ein Leerzeichen enthält?
Daniel A. White
4
Wie Daniel sagte, wird der Code in dieser Antwort leider nicht funktionieren, wenn eine der Dateien oder Ordner ein Leerzeichen oder eine neue Zeile im Namen enthält. Es zeigt einen sehr häufigen Missbrauch von forSchleifen und die typische Gefahr, die Ausgabe von zu analysieren ls. @ DanielA.Weiß, Sie könnten in Betracht ziehen, diese Antwort nicht zu akzeptieren, wenn sie nicht hilfreich ist (oder möglicherweise irreführend ist), da Sie, wie Sie sagten, Verzeichnisse bearbeiten . Die Antwort von Shawn J. Goff sollte eine robustere und funktionsfähigere Lösung für Ihr Problem bieten.
Slhck
4
-∞ Sie sollten die lsAusgabe nicht analysieren , Sie sollten die Ausgabe nicht in einer forSchleife lesen , Sie sollten $()anstelle von `` und Use More Quotes ™ verwenden . Ich bin sicher, @CoverosGene hat es gut gemeint, aber das ist einfach schrecklich.
l0b0
1
Eine bessere Alternative, wenn Sie wirklich verwenden möchten, lsist ls -1 | while read line; do stuff; done. Zumindest wird das nicht für Leerzeichen brechen.
Ejoubaud
1
mit mv -vIhnen brauchen nichtecho "moved $x -> $t"
DmitrySandalov
68

Die Ausgabe von lszu verwenden, um Dateinamen zu erhalten, ist eine schlechte Idee . Dies kann zu Fehlfunktionen und sogar zu gefährlichen Skripten führen. Dies liegt daran, dass ein Dateiname jedes Zeichen außer /und das nullZeichen enthalten kann und lskeines dieser Zeichen als Trennzeichen verwendet. Wenn ein Dateiname also ein Leerzeichen oder eine neue Zeile enthält, werden unerwartete Ergebnisse erzielt.

Es gibt zwei sehr gute Möglichkeiten, Dateien zu durchlaufen. Hier habe ich einfach echogezeigt, wie man etwas mit dem Dateinamen macht. Sie können jedoch alles verwenden.

Die erste besteht darin, die nativen Globbing-Funktionen der Shell zu verwenden.

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

Die Shell erweitert sich */in separate Argumente, die von der forSchleife gelesen werden. Auch wenn der Dateiname ein Leerzeichen, eine neue Zeile oder ein anderes merkwürdiges Zeichen enthält, forwird jeder vollständige Name als atomare Einheit angezeigt . Es analysiert die Liste in keiner Weise.

Wenn Sie rekursiv in Unterverzeichnissen gehen wollen, dann wird dies nicht tun , wenn Ihre Shell einige erweiterte Globbing Funktionen (zB bash‚s globstar. Wenn Ihre Shell nicht über diese Eigenschaften haben, oder wenn Sie wollen sicherstellen , dass Ihr Skript funktioniert auf einer Vielzahl von Systemen ist dann die nächste Option zu verwenden find.

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

Hier findruft der Befehl echoein Argument des Dateinamens auf und übergibt es. Dies wird einmal für jede gefundene Datei ausgeführt. Wie im vorherigen Beispiel wird keine Liste mit Dateinamen analysiert. Stattdessen wird ein Dateiformat vollständig als Argument gesendet.

Die Syntax des -execArguments sieht ein bisschen komisch aus. findNimmt das erste Argument nach -execund behandelt es als auszuführendes Programm. Jedes nachfolgende Argument wird als Argument zur Übergabe an dieses Programm verwendet. Es gibt zwei spezielle Argumente, -execdie man sehen muss. Der erste ist {}; Dieses Argument wird durch einen Dateinamen ersetzt, den die vorherigen Teile von findgenerieren. Die zweite ist ;, die mitteilt, finddass dies das Ende der Liste der Argumente ist, die an das Programm übergeben werden sollen. findbenötigt dies, weil Sie mit weiteren Argumenten fortfahren können, finddie für das ausgeführte Programm bestimmt sind und nicht. Der Grund dafür \ist, dass die Shell auch behandelt;Insbesondere - es stellt das Ende eines Befehls dar, daher müssen wir es maskieren, damit die Shell es als Argument gibt, findanstatt es für sich selbst zu verbrauchen. Eine andere Möglichkeit, die Shell dazu zu bringen, sie nicht speziell zu behandeln, besteht darin, sie in Anführungszeichen zu setzen: Funktioniert ';'genauso gut wie \;für diesen Zweck.

Shawn J. Goff
quelle
2
+1 Dies ist definitiv der richtige Weg, wenn Sie eine Dateiliste erstellen und in einem Befehl verwenden müssen. find -exec kann nur einzelne Befehle ausführen. Mit der Schleife können Sie nach Herzenslust pfeifen.
MaQleod
+1. Ich wünschte, mehr Leute würden verwenden find. Es gibt Magie, die getan werden kann, und sogar die wahrgenommenen Einschränkungen von -execkönnen umgangen werden. Die -print0Option ist auch nützlich für die Verwendung mit xargs.
Ghoti
Die Option loop zeigt keine versteckten Verzeichnisse an. Die Suchoption zeigt keine Symlinks an.
Jinawee
15

Bei Dateien mit Leerzeichen müssen Sie die Variable wie folgt angeben:

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

Sie können auch die Umgebungsvariable IFS (Input Field Separator) ändern:

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

Je nachdem, was Sie gerade tun, brauchen Sie möglicherweise nicht einmal das ls:

 for i in *; do echo "$i"; done;
John
quelle
nette Verwendung einer Subshell im ersten Beispiel
Jeremy L
Warum braucht IFS nach der Zuweisung und vor dem neuen Zeichen ein $?
Andy Ibanez
3
Dateinamen können auch Zeilenumbrüche enthalten. Einbrechen \nist unzureichend. Es ist niemals eine gute Idee, das Parsen der Ausgabe von zu empfehlen ls.
Ghoti
4

Um die Antwort von CoverosGene zu ergänzen, können Sie hier nur die Verzeichnisnamen auflisten:

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

Warum setzen Sie IFS nicht auf eine neue Zeile und erfassen dann die Ausgabe von lsin einem Array? Das Setzen von IFS auf newline sollte Probleme mit lustigen Zeichen in Dateinamen beheben. Die Verwendung lskann nützlich sein, da eine Sortierfunktion integriert ist.

(Beim Testen hatte ich Probleme, IFS auf zu setzen, \naber das Setzen auf Newline Backspace funktioniert, wie an anderer Stelle hier vorgeschlagen):

ZB (unter der Annahme, dass das gewünschte lsSuchmuster übergeben wurde $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

Dies ist besonders praktisch , auf OS X, zum Beispiel eine Liste von Dateien nach Erstellungsdatum (alt nach neu), der zu sortier erfassen lsBefehl ist ls -t -U -r.

Jetset
quelle
2
Dateinamen können auch Zeilenumbrüche enthalten. Dies ist häufig der Fall, wenn Benutzer ihre eigenen Dateien benennen dürfen. Einbrechen \nist unzureichend. Die einzig gültigen Lösungen verwenden eine for-Schleife mit Pfadnamenerweiterung oder find.
Ghoti
Die einzige zuverlässige Möglichkeit, eine Liste von Dateinamen zu übertragen, besteht darin, sie durch ein NUL-Zeichen zu trennen, da dies die einzige ist, die definitiv nicht in einem Dateipfad enthalten ist.
Glglgl
-3

So mache ich das, aber es gibt wahrscheinlich effizientere Möglichkeiten.

ls > filelist.txt

while read filename; do echo filename: "$filename"; done < filelist.txt
BAUM
quelle
6
Halten Sie sich an die Rohre anstelle der Datei:>ls | while read i; do echo filename: $i; done
Jeremy L
Cool. Ich sollte sagen, dass Sie auch $ EDITOR filelist.txt zwischen den beiden Befehlen verwenden können. Viele Dinge, die Sie in einem Editor tun können, sind einfacher als in der Befehlszeile. Für diese Frage jedoch nicht relevant.
BAUM
Ihre Lösung geht das Problem mit Dateinamen, die Zeilenumbrüche und andere ausgefallene Dinge enthalten, überhaupt nicht an.
Glglgl