sh-Syntax zum Verarbeiten von Nulldateien, die mit einem Platzhalter übereinstimmen, und mehr?

sh-Syntax zum Verarbeiten von Nulldateien, die mit einem Platzhalter übereinstimmen, und mehr?

Ich möchte ein /bin/shShell-Skript schreiben, das alle Dateien verarbeitet, die mit einem Platzhalter übereinstimmen. Es ist einfach, 1 oder mehr übereinstimmende Dateien zu verarbeiten. Ich finde es jedoch umständlich, den Fall von 0 übereinstimmenden Dateien zu verarbeiten.

Die offensichtliche Konstruktion ist:

#!/bin/sh
for f in *.ext; do
  handle "$f"
done

wobei *.extein oder mehrere Ausdrücke sein können, die die Shell mit Dateipfaden vergleicht, und handleein Shell-Befehl ist, der korrekt ausgeführt wird, wenn ein Pfad zu einer vorhandenen Datei angegeben wird, aber fehlschlägt, wenn ein Pfad angegeben wird, der keiner Datei zugeordnet werden kann. (In dem Fall, der diese Frage aufwirft, sind es *.flacund ffmpeg, aber ich denke, das spielt keine Rolle.)

Wenn es passende Dateien gibt foo.ext, bar.extdann führt dieses Skript aus

handle "foo.ext"
handle "bar.ext"

wie erwartet. Wenn es jedochnichtalle passenden Dateien, dieses Skript gibt eine Fehlermeldung wie,

handle: *.ext: No such file or directory

Ich glaube, ich verstehe, warum das passiert: dieSchlagmanpage(was meiner Meinung nach auch für gilt /bin/sh) besagt, dass die „Liste der folgenden Wörter inerweitert wird, wodurch eine Liste mit Elementen generiert wird. … Wenn die Erweiterung der folgenden Elemente in eine leere Liste resultiert, werden keine Befehle ausgeführt und der Rückgabestatus ist 0.“ Wenn *.ext„erweitert wird“, enthält die Ergebnisliste anscheinend *.ext, statt einer leeren Liste. Aber das erklärt nicht, wie man das verhindern kann.

(Update: Es gibt einesh (1)Manpage vom Heirloom Projectwas die Bourne Shell beschreibt sh, nicht die spätere Bourne Again Shell bash. UnterDateinamengenerierung, es heißt dort eindeutig: „Wenn kein Dateiname gefunden wird, der dem Muster entspricht, bleibt das Wort unverändert.“ Es erklärt, warum ein Muster in der Liste shverbleibt .)*.ext

Wie kann man diese Schleife kompakt und idiomatisch schreiben, sodass sie nullmal ohne Fehler ausgeführt wird, wenn keine übereinstimmenden Dateien vorhanden sind, aber einmal für jede übereinstimmende Datei? Oder noch besser: Was funktioniert mit mehreren Mustern, wie:

for f in *.ext1 special.ext1 *.ext2; do ...

Aus Portabilitätsgründen bevorzuge ich eine mit kompatible Syntax /bin/sh. Ich verwende zufällig Mac OS X 10.11.6, hoffe aber auf eine Syntax, die auf jedem Unix-ähnlichen Betriebssystem funktioniert.

Mir sind ein paar unbeholfene, nicht idiomatische Möglichkeiten eingefallen, eine solche Schleife zu schreiben. Wenn ich nicht sofort gute Antworten bekomme, werde ich diese der Vollständigkeit halber als Antworten beisteuern.

Antwort1

Der einfachste portable Weg hierfür besteht darin, die Schleife zu überspringen, wenn die Erweiterung nichts erzeugt, was tatsächlich existiert:

for f in *.ext1 *.ext2; do
  [ -e "$f" ] || continue
  handle "$f"
done

Erklärung: Angenommen, es gibt mehrere .ext2-Dateien, aber keine .ext1-Dateien. In diesem Fall werden die Platzhalter zu erweitert *.ext1 filea.ext2 fileb.ext2 filec.ext2. Dies [ -e "*.ext1" ]schlägt fehl und führt aus continue, wodurch der Rest dieser Iteration der Schleife übersprungen wird. Dann [ -e "filea.ext2" ]ist usw. erfolgreich und diese Iterationen der Schleife werden normal ausgeführt.

Übrigens können Sie dies ändern, um beispielsweise [ -f "$f" ] || continuealles zu überspringen, was keine einfache Datei ist (wenn Sie Verzeichnisse usw. überspringen möchten).

Wenn Sie unter Bash arbeiten, können Sie natürlich shopt -s nullglobzuerst ausführen, damit keine nicht übereinstimmenden Muster in der Liste verbleiben. Und dann shopt -u nullglobdanach, um unerwartete Nebenwirkungen zu vermeiden (z. B. grep pattern *.nonexistentwird versucht, von stdin zu lesen, wenn nullglobfestgelegt ist).

Antwort2

Wechseln Sie von der Bourne Shell () zur Bourne Again Shell /bin/bash( /bin/sh), und eine einfache Lösung wird möglich.

Derbash(1)manpageerwähnt dienullglobMöglichkeit:

Wenn gesetzt, erlaubt bash Muster, die mit keiner Datei übereinstimmen (siehePfadnamenerweiterungoben) so erweitert werden, dass eine Nullzeichenfolge und nicht die Erweiterung selbst entsteht.

DerPfadnamenerweiterungAbschnitt sagt:

Nach der Worttrennung durchsucht …bash jedes Wort nach den Zeichen*,?, Und[. Wenn eines dieser Zeichen vorkommt, wird das Wort als Muster betrachtet und durch eine alphabetisch sortierte Liste von Dateinamen ersetzt, die dem Muster entsprechen. Wenn keine übereinstimmenden Dateinamen gefunden werden und die Shell-Optionnullglobnicht aktiviert ist, bleibt das Wort unverändert. Wenn dienullglobOption aktiviert ist und keine Übereinstimmungen gefunden werden, wird das Wort entfernt.…

Wenn Sie also dienullglobOption gibt das gewünschte Verhalten für Muster an. Wenn keine Datei dem Muster entspricht, wird die Schleife nicht ausgeführt.

#!/bin/bash
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done

Aber dienullglobOption kann das gewünschte Verhalten für andere Befehle beeinträchtigen. Daher ist es wahrscheinlich sinnvoll, die vorhandene Einstellung vonnullglobund danach wiederherstellen. Glücklicherweise gibt der shopt -peingebaute Befehl die Ausgabe in einer Form aus, die als Eingabe wiederverwendet werden kann. Aber er gibt die Ausgabe für alle Optionen aus, also verwenden Sie , grepum dienullobjEinstellung. Siehe das Protokoll unten (wobei $die Bash-Eingabeaufforderung angezeigt wird):

$ shopt -p | grep nullglob
shopt -u nullglob
$ shopt -s nullglob
$ shopt -p | grep nullglob
shopt -s nullglob

Die endgültige Bash-Syntax zur Verarbeitung von null oder mehr Dateien, die einem Platzhaltermuster entsprechen, sieht also folgendermaßen aus:

#!/bin/bash
SAVED_NULLGLOB=$(shopt -p | grep nullglob)
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done
eval "$SAVED_NULLGLOB"

Außerdem wurden in der Frage mehrere Muster erwähnt, wie:

for f in *.ext1 special.ext1 *.ext2; do ...

DernullglobOption hat keinen Einfluss auf ein Wort in der Liste, das kein „Muster“ ist, und special.ext1wird daher trotzdem an die Schleife übergeben. Die einzige Lösung hierfür ist@Gordon Davisson's continueAusdruck, [ -e "$f" ] || continue.

Anerkennung: beide@Ignacio Vazquez-AbramsUnd@Gordon Davissonangedeutet bash(1)und seinenullglobOption. Danke. Da sie nicht sofort eine Antwort mit einem vollständig ausgearbeiteten Beispiel beigesteuert haben, mache ich das selbst.

verwandte Informationen