Warum ist eine Shell eingebaut und ein Shell-Schlüsselwort?

64

Soweit ich weiß, [[handelt es sich um eine erweiterte Version von [, aber ich bin verwirrt, wenn ich [[als Schlüsselwort sehe und [als eingebaut angezeigt werde.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP sagt

Ein Builtin kann ein Synonym für einen Systembefehl mit demselben Namen sein, Bash implementiert ihn jedoch intern neu. Beispielsweise ist der Befehl "Bash echo" nicht mit "/ bin / echo" identisch, obwohl sein Verhalten nahezu identisch ist.

und

Ein Schlüsselwort ist ein reserviertes Wort, ein Token oder ein Operator. Schlüsselwörter haben eine besondere Bedeutung für die Shell und sind in der Tat die Bausteine ​​der Shell-Syntax. Als Beispiele für, while, do und! sind Schlüsselwörter. Ähnlich wie ein eingebautes Schlüsselwort ist ein Schlüsselwort fest in Bash codiert, aber im Gegensatz zu einem eingebauten Schlüsselwort ist ein Schlüsselwort an sich kein Befehl, sondern eine Untereinheit eines Befehlskonstrukts. [2]

Sollte das nicht beides [und [[ein Schlüsselwort sein? Fehlt mir hier etwas? Außerdem bestätigt dieser Link , dass beide zur selben Art gehören [und [[gehören sollten.

Sree
quelle
2
Siehe unix.stackexchange.com/a/56674
Stéphane Chazelas
9
/ bin / [existiert auf meinem Rechner.
Joshua
2
Als einfache Demonstration eines Unterschieds zwischen den beiden: Funktioniert if "[" $x -eq 3 ]wie erwartet (weil Bash nach dem aufgerufenen Befehl sucht [und dieser existiert), if "[[" $x -eq 3 ]]funktioniert aber nicht (weil Bash erneut nach einem Befehl mit dem entsprechenden Namen sucht, aber es gibt keinen [[Befehl).
Kyle Strand
1
@Joshua Tut es auch /usr/bin/echo, aber das heißt nicht, dass es nicht eingebaut ist .
Jonathon Reinhart
Alle Bulitins, die auch extern existieren, analysieren Argumente, wenn sie keine Buildins sind.
Joshua

Antworten:

80

Der Unterschied zwischen [und [[ist sehr grundlegend.

  • [ist ein Befehl. Seine Argumente werden genauso verarbeitet wie alle anderen Befehlsargumente. Betrachten Sie zum Beispiel:

    [ -z $name ]

    Die Shell wird erweitert $nameund führt sowohl die Wortteilung als auch die Dateinamengenerierung für das Ergebnis durch, genau wie für jeden anderen Befehl.

    Beispielsweise schlägt Folgendes fehl:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments

    Damit dies korrekt funktioniert, sind Anführungszeichen erforderlich:

    $ [ -n "$name" ] && echo not empty
    not empty
  • [[ist ein Shell-Schlüsselwort und seine Argumente werden nach speziellen Regeln verarbeitet. Betrachten Sie zum Beispiel:

    [[ -z $name ]]

    Die Shell wird erweitert, führt $namejedoch im Gegensatz zu anderen Befehlen weder eine Wortteilung noch eine Dateinamengenerierung für das Ergebnis durch. Beispielsweise wird trotz der eingebetteten Leerzeichen Folgendes erfolgreich sein name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty

Zusammenfassung

[ ist ein Befehl und unterliegt den gleichen Regeln wie alle anderen Befehle, die die Shell ausführt.

Da [[es sich um ein Schlüsselwort und nicht um einen Befehl handelt, wird es von der Shell speziell behandelt und unter sehr unterschiedlichen Regeln ausgeführt.

John1024
quelle
+1. Vielen Dank. Können Sie die Quelle (Referenz) angeben, unter welchen Regeln ein Befehl und ein Schlüsselwort ausgeführt werden?
Tim
@Tim Die Regeln, unter denen Befehle ausgeführt werden , sind in aufgeführt man bash. Siehe insbesondere die Abschnitte "EINFACHE BEFEHLSERWEITERUNG" und "BEFEHLSAUSFÜHRUNG". Zusätzlich zu [[, andere bash Schlüsselwörter enthalten if, then, while, und ‚Fall‘. Es gibt keine allgemeinen Regeln für Schlüsselwörter: Jedes Schlüsselwort ist ein Sonderfall. man bash enthält Details für jeden.
John1024
62

In V7 wurde Unix - wo die Bourne-Shell ihr Debüt gab - [aufgerufen testund existierte nur als /bin/test. Code, den Sie heute schreiben würden als:

if [ "$foo" = "bar" ] ; then ...

du hättest stattdessen geschrieben als

if test "$foo" = "bar" ; then ...

Diese zweite Notation existiert immer noch und ich finde, dass es klarer ist, was los ist: Sie rufen einen Befehl auf test, der seine Argumente auswertet und einen Exit-Statuscode zurückgibt , der ifverwendet, um zu entscheiden, was als nächstes zu tun ist. Dieser Befehl kann in die Shell eingebaut sein, oder es kann sich um ein externes Programm handeln.¹

[als Alternative zu testspäter gekommen. Es kann ein eingebautes Synonym für sein test, aber es wird auch wie /bin/[auf modernen Systemen für Schalen bereitgestellt , die es nicht als eingebautes haben.

[und testkann unter Verwendung des gleichen Codes implementiert werden. Dies ist bei /bin/[und /bin/testunter OS X der Fall , bei denen es sich um feste Verknüpfungen zu derselben ausführbaren Datei handelt.³ Folglich ignoriert die Implementierung das nachfolgende Element vollständig ]: Es ist nicht erforderlich, wenn Sie es als aufrufen /bin/[, und es beklagt sich nicht wenn Sie es bereitstellen /bin/test.⁴

Nichts davon hat Auswirkungen auf die Geschichte [[, da nie ein ursprüngliches Programm aufgerufen wurde [[. Es existiert nur in den Shells, die es als Erweiterung der POSIX-Shell implementieren .

Ein Teil der Unterscheidung zwischen "builtin" und "keyword" ist auf diese Historie zurückzuführen. Dies spiegelt auch die Tatsache wider, dass die Syntaxregeln für das Parsen von [[Ausdrücken unterschiedlich sind, wie in der Antwort von John1024 ausgeführt


Fußnoten:

  1. Wenn Sie es so betrachten, wird deutlich, warum Sie [in Shell-Skripten Leerzeichen einfügen müssen, im Gegensatz zu der Art und Weise, wie Klammern und Klammern in den meisten anderen Programmiersprachen funktionieren. Wenn der Befehlsparser der Shell if["$x"...dies zulässt, muss er dies auch zulasseniftest"$x"...

  2. Es geschah um 1980. /bin/[In meiner Kopie von Ancient Unix V7 aus dem Jahr 1979 ist es weder vorhanden noch man testals Alias ​​dokumentiert. In dem entsprechenden Manpage Eintrag I in einer Pre-Release - Kopie des habe System III von 1980 Handbuch, es ist aufgeführt.

  3. ls -i /bin/[ /bin/test

  4. Aber rechnen Sie nicht mit diesem Verhalten. Die in Bash integrierte Version von [erfordert das Schließen ], und die integrierte testImplementierung wird sich beschweren, wenn Sie sie bereitstellen.

  5. Die Unterscheidung zwischen integrierten und externen Befehlen kann auch aus einem anderen Grund von Bedeutung sein: Die beiden Implementierungen können sich unterschiedlich verhalten. Dies ist bei echovielen Systemen der Fall . Da es nur eine Implementierung gibt, muss für ein Schlüsselwort keine solche Unterscheidung getroffen werden.

Warren Young
quelle
Danke @Warren Young, aber warum die builtinund keywordUnterscheidung zwischen [und [[wenn beide bieten die gleiche Funktionalität ( mit Ausnahme der Tatsache , dass [[mit mehr Funktionen kommt als [)?
Sree
2
@sree [[ist ein Schlüsselwort, mit dem bash Dinge erledigen kann, mit denen es nicht möglich ist. [Beispielsweise ist das Zitieren nicht viel Zeit erforderlich, da die Shell weiß, dass es sich um eine Variable handelt. Das heißt, die Verarbeitung der Befehlszeile wird beeinflusst, wenn ein Schlüsselwort verwendet wird, nicht jedoch, wenn ein eingebautes Schlüsselwort verwendet wird - das geschieht viel später.
muru
1
Beachten Sie, dass der Code für die V7-Bourne-Shell eine integrierte [Version enthält, der Code jedoch auskommentiert ist.
Stéphane Chazelas
cdist ein eingebautes, aber es spiegelt nichts ( cdkann nicht als externes Programm implementiert werden).
Paŭlo Ebermann
2
Dies ist eine ausgezeichnete historische Zusammenfassung, aber sie lässt das entscheidende Detail (IMO) unberücksichtigt, auf das muru oben und John1024 in ihrer Antwort hingewiesen haben, dass das Erstellen [[eines Schlüsselworts es der Shell ermöglicht, spezielle Parsing-Regeln für ihre Argumente zu verwenden. Leider geht meine Gegenstimme an John1024.
Ilmari Karonen
3

[war ursprünglich nur ein externer Befehl, ein anderer Name für /bin/test. Einige Befehle wie [und echowerden jedoch in Shell-Skripten so häufig verwendet, dass die Shell-Implementierer beschlossen, den Code direkt in die Shell selbst zu kopieren, anstatt bei jeder Verwendung einen anderen Prozess ausführen zu müssen. Dadurch wurden diese Befehle zu "eingebauten" Befehlen, obwohl Sie das externe Programm weiterhin über seinen vollständigen Pfad aufrufen können.

[[kam viel später. Obwohl das Builtin intern in der Shell implementiert ist, wird es genau wie externe Befehle analysiert. Wie in John1024 Antwort erklärt, bedeutet dies , dass nicht notieren Variablen Wort Spaltung auf sie getan wird, und Token wie >und <als I / O - Umleitung verarbeitet. Dies machte das Schreiben komplexer Vergleichsausdrücke unbequem. [[wurde als Shell-Syntax erstellt, damit sie ideosynkratisch analysiert werden kann. Innerhalb von [[Variablen wird kein Wort getrennt <und sie >können als Vergleichsoperatoren =verwendet werden. Je nachdem, ob der nächste Parameter in Anführungszeichen steht oder nicht , kann sich das Verhalten unterschiedlich verhalten. Dies sind alles Bequemlichkeiten, die [[die Verwendung einfacher machen als beim herkömmlichen [Befehl / Builtin.

Sie konnten [die Syntax nicht einfach so umkodieren, da dies eine inkompatible Änderung an Millionen von Skripten gewesen wäre. Durch die Verwendung der neuen [[Syntax, die es bisher nicht gab, konnte die Verwendung aufwärtskompatibel völlig überarbeitet werden.

Dies ähnelt der Entwicklung, die zu einer $((...))Syntax für arithmetische Ausdrücke führte, die den traditionellen exprBefehl größtenteils ersetzt hat .

Barmar
quelle
0

Das neuere [[in bashist eine Optimierung von [.

Der Klassiker [hat einen großen Nachteil, wenn er häufig für einfache Operationen verwendet wird:
Er erzeugt jedes Mal einen neuen Prozess: (Er erstellt einen neuen Adressraum nur zum Vergleichen 0und 1! Jedes Mal!)

Ich denke, ein Hauptaspekt des Hinzufügens von [[bestand darin, die Bewertung des Ausdrucks im Inneren [nicht als zusätzlichen Prozess zu betrachten. Aber wie [es funktioniert, könnte nicht geändert werden - es würde viel Verwirrung und Probleme verursachen. Daher wurde die Optimierung auf effizientere Weise unter einem neuen Namen implementiert, nämlich einem Shell-Builtin-Befehl.
Als Nebeneffekt wurde es zum Schlüsselwort in der Shell-Syntax.

Zu der Zeit [wurde zunächst verwendet, es war der richtige Weg , dies mit einem externen Prozess zu tun.

Volker Siegel
quelle
5
Beachten Sie, dass es sich zwar [ursprünglich um einen externen Befehl handelte, dieser jedoch schon sehr früh als integrierter Bestandteil der Shell hinzugefügt wurde, wahrscheinlich zu der Zeit, als Unix System III und definitiv vor der Veröffentlichung von Unix System V. Der "Extra-Prozess" war also schon seit Ewigkeiten kein Problem mehr. Die Syntax blieb jedoch unverändert - es wurde so behandelt, als wäre es ein externer Befehl.
Jonathan Leffler
@ JonathanLeffler Oh, danke, ich habe verpasst, dass [das beides ist - das bedeutet einige Änderungen ...
Volker Siegel