Warum ist nullglob nicht voreingestellt?

61

In den meisten Shells nullglobist dies nicht der Standard. Das heißt zum Beispiel, wenn Sie diesen Befehl ausführen

ls *

In einem leeren Verzeichnis wird der *Glob zu einem Literal *und nicht zu einer leeren Liste von Argumenten erweitert. Es gibt Möglichkeiten, dieses Verhalten zu ändern, sodass *in einem leeren Verzeichnis eine leere Liste von Argumenten zurückgegeben wird, die intuitiver zu sein scheinen.

Gibt es einen Grund, warum nullglobstandardmäßig deaktiviert ist? Wenn ja, aus welchem ​​Grund?

Dakkaron
quelle
13
Jemand hat einmal eine schlechte Wahl getroffen, die zu "Wir haben es immer so gemacht" führte. Ein sehr allgegenwärtiges Phänomen (nicht nur) in der Software-Welt.
PSkocik
4
Der ursprüngliche Grund ist, dass die Option nullglob damals nicht existierte. Um die Abwärtskompatibilität zu gewährleisten, muss diese standardmäßig deaktiviert sein.
PM 2Ring
3
Obwohl der Null-Glob nicht ausdrücklich erwähnt wird, enthält diese Antwort einige
Informationen
4
Ich habe den Eindruck, dass es offensichtlich sein sollte, dass nullglob standardmäßig aktiviert ist. Ich denke nicht, dass es offensichtlich ist. Dass Erweiterungen in der Shell vor dem Befehlsaufruf erfolgen, bedeutet, dass das Erweitern auf nichts weniger intuitiv ist als das unveränderte Glob.
Kojiro
2
@kojiro Weniger intuitiv für wen? Jeder, der mit * NIX-Shells vertraut ist, weiß, dass *es sich um ein Glob handelt, und es wird auf alle vorhandenen Dateien ausgeweitet . Wie ist es "intuitiv", wenn in einem speziellen Fall leere Verzeichnis-Globs zu einem Literal "erweitert" werden *?
Kyle Strand

Antworten:

78

Die nullglobOption (bei der es sich übrigens um eine zshErfindung handelt, die erst Jahre später zu bash( 2.0) hinzugefügt wurde ) wäre in einigen Fällen nicht ideal. Und lsist ein gutes Beispiel:

ls *.txt

Oder sein korrekteres Äquivalent:

ls -- *.txt

Mit nullglobon würde lsohne Argument ausgeführt, das wie folgt behandelt wird ls -- .(das aktuelle Verzeichnis auflisten), wenn keine Dateien übereinstimmen. Dies ist wahrscheinlich schlimmer als der Aufruf lsmit einem Literal *.txtals Argument.

Bei den meisten Textdienstprogrammen treten ähnliche Probleme auf:

grep foo *.txt

Würde fooauf stdin suchen, wenn es keine txtDatei gibt.

Eine vernünftigere Standardeinstellung, und zwar csh, tcsh, zsh oder fish 2.3+ (und frühere Unix-Shells), besteht darin, den Befehl insgesamt abzubrechen, wenn der Glob nicht übereinstimmt.

bash(ab Version 3) eine failglobOption für die (interessant für diese Diskussion, da im Gegensatz zu ashAT & T kshoder zsh, bashnicht lokalen Bereichen für Optionen unterstützen (obwohl das in 4.4 zu ändern ist), diese Option , wenn aktivierte global ein paar Dinge nicht bricht wie die Bash-Completion-Funktionen).

Beachten Sie, dass csh und tcsh leicht unterschiedlich sind zsh, fishoder bash -O failglobin Fällen wie:

ls -- *.txt *.html

Hier müssen alle Globs nicht übereinstimmen, damit der Befehl abgebrochen wird. Wenn es zum Beispiel eine TXT-Datei und keine HTML-Datei gibt, wird dies zu:

ls -- file.txt

Sie können dieses Verhalten bekommen zshmit , setopt cshnullglobobwohl ein vernünftiger Weg , es zu tun in zshsein würde wie ein Klacks zu verwenden:

ls -- *.(txt|html)

In zshund ksh93können Sie nullglob auch global anwenden. Dies ist weitaus vernünftiger als das Ändern einer globalen Einstellung:

files=(*.txt(N))  # zsh
files=(~(N)*.txt) # ksh93

würde ein leeres Array erstellen, wenn es keine txtDatei gäbe, anstatt den Befehl mit einem Fehler fehlzuschlagen (oder es zu einem Array mit einem *.txtLiteralargument mit anderen Shells zu machen).

Versionen fishvor 2.3 würden funktionieren bash -O nullglob, geben aber eine Warnung aus, wenn sie interaktiv sind, wenn ein Glob keine Übereinstimmung hat. Seit 2.3, funktioniert es wie mit zshAusnahme von Klümpchen verwendet in for, setoder count.

In der Verlaufsnotiz wurde das Verhalten tatsächlich von der Bourne-Shell unterbrochen . In früheren Unix-Versionen wurde das Globbing über den /etc/globHelper ausgeführt, und dieser Helper verhielt sich wie cshfolgt: Der Befehl schlug fehl, wenn keiner der Globs mit einer Datei übereinstimmte, und entfernte die Globs, wenn keine andere Übereinstimmung bestand.

Die Situation, in der wir uns heute befinden, ist auf eine schlechte Entscheidung in der Bourne-Shell zurückzuführen.

Beachten Sie, dass die Bourne-Shell (und die C-Shell) eine weitere neue Unix-Funktion enthielten: die Umgebung. Das bedeutete variable Erweiterung (der Vorgänger hatte nur die $1, $2... Positionsparameter). Die Bourne-Shell führte auch die Befehlsersetzung ein.

Eine weitere schlechte Designentscheidung der Bourne-Shell war das Durchführen von Globbing (und Splitting) bei der Erweiterung von Variablen und der Befehlssubstitution (möglicherweise aus Gründen der Abwärtskompatibilität mit der Thompson-Shell, bei echo $1der /etc/globbei Verwendung von $1Platzhaltern immer noch aufgerufen wird) da wurde wie im expandierten wert nochmal als shell code geparst)).

Fehlgeschlagene Globs, die nicht übereinstimmen, bedeuten zum Beispiel Folgendes:

pattern='a.*b'
grep $pattern file

würde den Befehl nicht ausführen (es sei denn, a.whateverbdas aktuelle Verzeichnis enthält einige Dateien). csh(das auch Globbing bei variabler Erweiterung ausführt) scheitert in diesem Fall am Befehl (und ich würde behaupten, es ist besser, als dort einen ruhenden Fehler zu hinterlassen, auch wenn es nicht so gut ist, als würde man überhaupt kein Globbing wie in ausführen zsh).

Stéphane Chazelas
quelle
Es gibt ein weiteres Usability-Problem: nullglobScheint, die Tab-Vervollständigung zu unterbrechen (das Drücken der Tab-Taste führt zu nichts, wenn sie aktiviert ist).
Kyle Strand
1
Es ist möglich, nullglob pro Globus mit bash zu verwenden - obwohl die Syntax nicht so elegant ist wie zshfiles=$(shopt -s nullglob;echo *.txt)
Jon Nalley
2
@ JonNalley, das die Verkettung (mit Leerzeichen) der Dateinamen (mit möglicher Umwandlung mit xpg_echo) in skalare Variablen speichert . Man braucht so etwas wie readarray -td '' files < <(shopt -s nullglob; printf '%s\0' *.txt)mit bash4.4 oder höher oder (shopt -s nullglob; printf '%s\0' *.txt) | xargs -r0 cmdmit GNU xargs, um überhaupt mit beliebigen Dateinamen arbeiten zu können. Oder verwenden Sie noch mit bash4.4 eine Hilfsfunktion, die local -(25 Jahre später von ash kopiert) einen lokalen Bereich für Optionen verwendet.
Stéphane Chazelas