Was ist der Unterschied zwischen Shell Builtin und Shell Keyword?

33

Wenn ich diese beiden Befehle ausführe, bekomme ich

$ type cd
cd is a shell builtin
$ type if
if is a shell keyword

Es wird deutlich gezeigt, dass cdes sich um eine eingebaute Shell und ifein Shell-Schlüsselwort handelt. Was ist der Unterschied zwischen Shell Builtin und Keyword?

Avinash Raj
quelle
2
related: unix.stackexchange.com/questions/267761/…
Ciro Santilli am

Antworten:

45

Es gibt einen starken Unterschied zwischen einem eingebauten und einem Schlüsselwort, was die Art und Weise betrifft, wie Bash Ihren Code analysiert. Bevor wir über den Unterschied sprechen, listen wir alle Schlüsselwörter und Builtins auf:

Builtins:

$ compgen -b
.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait                          

Schlüsselwörter:

$ compgen -k
if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc              

Beachten Sie, dass es sich beispielsweise [um ein eingebautes [[Schlüsselwort handelt. Ich werde diese beiden verwenden, um den folgenden Unterschied zu veranschaulichen, da es sich um bekannte Operatoren handelt: Jeder kennt sie und verwendet sie regelmäßig (oder sollte).

Ein Schlüsselwort wird von Bash sehr früh in der Analyse gescannt und verstanden. Dies ermöglicht zum Beispiel Folgendes:

string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
    echo "The string is non-empty"
fi

Dies funktioniert gut, und Bash wird glücklich ausgeben

The string is non-empty

Beachten Sie, dass ich nicht zitiert habe $string_with_spaces. In der Erwägung, dass

string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
    echo "The string is non-empty"
fi

zeigt, dass Bash nicht glücklich ist:

bash: [: too many arguments

Warum funktioniert es mit Schlüsselwörtern und nicht mit eingebauten? Denn wenn Bash den Code analysiert, sieht er, [[welches ein Schlüsselwort ist, und versteht sehr früh, dass es etwas Besonderes ist. Es wird also nach dem Verschluss suchen ]]und das Innere auf besondere Weise behandeln. Ein eingebauter (oder Befehl) wird als tatsächlicher Befehl behandelt, der mit Argumenten aufgerufen wird. In diesem letzten Beispiel geht bash davon aus, dass der Befehl [mit Argumenten ausgeführt werden sollte (eines pro Zeile):

-n
some
spaces
here
]

da es zu variabler Erweiterung, Entfernung von Anführungszeichen, Erweiterung von Pfadnamen und Wortteilung kommt. Der Befehl [wird in der Shell erstellt, also führt er ihn mit diesen Argumenten aus, was zu einem Fehler und damit zur Beschwerde führt.

In der Praxis sehen Sie, dass diese Unterscheidung ein ausgeklügeltes Verhalten ermöglicht, das mit integrierten Funktionen (oder Befehlen) nicht möglich wäre.

Wie kann man in der Praxis ein eingebautes von einem Schlüsselwort unterscheiden? Das ist ein lustiges Experiment:

$ a='['
$ $a -d . ]
$ echo $?
0

Wenn Bash die Zeile analysiert $a -d . ], sieht es nichts Besonderes (dh keine Aliase, keine Umleitungen, keine Schlüsselwörter), so dass es nur eine variable Erweiterung durchführt. Nach variablen Erweiterungen sieht es:

[ -d . ]

Führt also den Befehl (builtin) [mit Argumenten aus -d, .und ]das ist natürlich wahr (dies testet nur, ob .es sich um ein Verzeichnis handelt).

Schau jetzt:

$ a='[['
$ $a -d . ]]
bash: [[: command not found

Oh. Das liegt daran, dass Bash, wenn er diese Zeile sieht, nichts Besonderes sieht und daher alle Variablen erweitert und schließlich sieht:

[[ -d . ]]

Zu diesem Zeitpunkt wurden Alias-Erweiterungen und das Durchsuchen von Schlüsselwörtern schon lange durchgeführt und werden nicht mehr durchgeführt. Bash versucht daher, den aufgerufenen Befehl zu finden [[, findet ihn nicht und beschwert sich.

Auf der gleichen Linie:

$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found

und

$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found

Auch die Alias-Erweiterung ist etwas Besonderes. Sie haben alle mindestens einmal Folgendes getan:

$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

Die Überlegung ist dieselbe: Die Alias-Erweiterung erfolgt lange vor der variablen Erweiterung und der Entfernung von Anführungszeichen.


Keyword vs Alias

Was passiert Ihrer Meinung nach, wenn wir einen Alias ​​als Schlüsselwort definieren?

$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0

Oh, es funktioniert! Aliase können also verwendet werden, um Keywords zu aliasen! gut zu wissen.


Fazit: Builtins verhalten sich wirklich wie Befehle: Sie entsprechen einer Aktion, die mit Argumenten ausgeführt wird, die einer direkten Variablenerweiterung, Wortteilung und Globbing unterzogen werden. Es ist wirklich wie in einem externen Befehl irgendwo mit /binoder /usr/bindass mit den Argumenten nach variable Expansion gegeben genannt wird, usw. Beachten Sie, dass , wenn ich sage , es ist wirklich wie ein externer Befehl, die ich nur mit Bezug auf Argumente, einzelne Wörter aufgespalten, bedeuten Globbing, variable Erweiterung, etc. Ein Builtin kann den internen Zustand der Shell verändern!

Andererseits werden Schlüsselwörter sehr früh gescannt und verstanden und ermöglichen ein ausgeklügeltes Shell-Verhalten: Die Shell kann die Aufteilung von Wörtern oder die Erweiterung von Pfadnamen usw. verbieten.

Schauen Sie sich nun die Liste der integrierten Funktionen und Schlüsselwörter an und versuchen Sie herauszufinden, warum einige Schlüsselwörter sein müssen.


!ist ein Schlüsselwort. Es scheint möglich zu sein, sein Verhalten mit einer Funktion nachzuahmen:

not() {
    if "$@"; then
        return false
    else
        return true
    fi
}

aber das würde Konstrukte wie verbieten:

$ ! ! true
$ echo $?
0

oder

$ ! { true; }
echo $?
1

Dasselbe gilt für time: Es ist leistungsfähiger, ein Schlüsselwort zu haben, damit komplexe zusammengesetzte Befehle und Pipelines mit Umleitungen zeitlich festgelegt werden können:

$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null

Wenn timenur ein Befehl (auch eingebaut) vorhanden wäre, würden nur die Argumente angezeigt grep, ^#und zu gegebener /home/gniourf/.bashrcZeit würde seine Ausgabe die verbleibenden Teile der Pipeline durchlaufen. Aber mit einem Schlüsselwort kann Bash alles bewältigen! es kann timedie komplette pipeline inklusive der umleitungen! Wenn timees nur ein Befehl wäre, könnten wir Folgendes nicht tun:

$ time { printf 'hello '; echo world; }

Versuch es:

$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'

Versuch es zu reparieren:

$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory

Hoffnungslos.


Keyword gegen Alias?

$ alias mytime=time
$ alias myls=ls
$ mytime myls

Was denkst du passiert?


In Wirklichkeit ist ein Builtin wie ein Befehl, nur dass er in die Shell integriert ist, während ein Schlüsselwort ein ausgefeiltes Verhalten zulässt! Wir können sagen, dass es Teil der Grammatik der Shell ist.

gniourf_gniourf
quelle
2
In Übereinstimmung mit @JohnyTex ist dies eine der umfassendsten und angemessensten Antworten, die ich auf den Stack-Sites gesehen habe. Vielen Dank. Eine vielleicht nicht verwandte Frage: nur für Neugier willen Ich versuche , die Dokumentation für den ‚vorübergehend deaktivieren alias‘ -Funktionalität zu finden , von den vorhergehenden einen Befehl mit =\'durch die Verwendung man, aproposund helpich habe nicht hatte kein Glück. Irgendeine Idee, wo ich diese Informationen finden würde? Meistens, damit ich in Zukunft sehen kann, was sonst noch drin ist, weil ich glaube, dass ich eine Referenzquelle vermisse.
nc.
@nc: Sie werden es nicht explizit dokumentiert finden. Der Grund, warum es funktioniert, wird in dieser Antwort erklärt. Die nächstgelegene Anleitung finden Sie im Referenzhandbuch im Abschnitt Shell-Betrieb . Sie werden sehen, dass die Alias-Erweiterung sehr früh erfolgt (etwas, das ich in dieser Antwort hervorgehoben habe), in Schritt 2. Die Entfernung der Anführungszeichen, die Erweiterung der Parameter, das Globbing usw. werden später durchgeführt. So deaktivieren einen Alias können Sie eine Art zu zitieren verwenden zu verbieten die Schale aus dem Verständnis eines Tokens als Alias, zum Beispiel \ll, "ll"oder 'll'.
gniourf_gniourf
1
Ich habe tatsächlich die Bestellung erhalten, Aliase und Schlüsselwörter werden zuerst ausgelöst. Da wir das [[Ergebnis zitieren , wird \[[es nicht als Alias ​​analysiert. Bis jetzt richtig? Als ich mich verlaufen habe, war mir nicht klar, dass der Backslash in Bash ein Zitat ist, und ich habe versucht, ihn unter Aliasnamen und Schlüsselwörtern nachzuschlagen und bin völlig verloren gegangen ... Alles gut jetzt. Im Abschnitt Zitieren:> Ein nicht in Anführungszeichen stehender Backslash () ist das Escape-Zeichen.
nc.
1
Wir können uns also (2) in dieser Op-Liste als \[[Tokenization vorstellen und stellen ein einzelnes Token vom Typ LiteralQuoteToken vor, im Gegensatz [[zu einem OpenTestKeywordToken, ]]für dessen Kompilierung / korrekte Syntax CloseTestKeywordToken erforderlich ist . Später wird in (4) das LiteralQuoteToken [[als Name des auszuführenden Befehls ausgewertet , und in (6) wird Bash gesperrt, weil es keinen [[eingebauten Befehl gibt. Nett. Ich kann die genauen Details im Laufe der Zeit vergessen, aber in diesem Moment ist mir die Art und Weise, wie Bash Dinge ausführt, viel klarer. Danke dir.
nc.
9

man bashruft sie an SHELL BUILTIN COMMANDS. Eine "eingebaute Shell" ist also wie ein normaler Befehl grepusw., aber anstatt in einer separaten Datei enthalten zu sein, ist sie in bash selbst eingebaut . Dadurch arbeiten sie effizienter als externe Befehle.

Ein Schlüsselwort ist auch "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." Ich interpretiere dies so, dass Schlüsselwörter allein keine Funktion haben, aber Befehle erfordern, um etwas zu tun. (Aus dem Link, weitere Beispiele sind for, while, do, und !, und es gibt mehr in meiner Antwort auf Ihre andere Frage.)

Sparhawk
quelle
1
Interessante Tatsache: [[ is a shell keywordaber [ is a shell builtin. Ich habe keine Idee warum.
Sparhawk
1
Aus historischen Gründen ist es wahrscheinlich, dass der POSIX-Standard so gut wie möglich mit der alten Bourne-Shell übereinstimmt, da er [früher als separater Befehl existierte. [[Wird vom Standard nicht spezifiziert, so können die Entwickler wählen, ob sie das als Schlüsselwort oder als eingebautes Schlüsselwort verwenden möchten.
Sergiy Kolodyazhnyy
1

Das mit Ubuntu gelieferte Befehlszeilenhandbuch enthält keine Definition von Schlüsselwörtern. Das Online-Handbuch (siehe Abschnitt) und die Standardspezifikationen für POSIX Shell Command Language bezeichnen diese als "Reserved Words" (Reservierte Wörter) und enthalten beide Listen. Aus dem POSIX-Standard:

Diese Erkennung darf nur erfolgen, wenn keines der Zeichen in Anführungszeichen steht und das Wort verwendet wird als:

  • Das erste Wort eines Befehls

  • Das erste Wort nach einem der reservierten Wörter außer case, for oder in

  • Das dritte Wort in einem case-Befehl (nur in ist in diesem Fall gültig)

  • Das dritte Wort in einem for-Befehl (nur in und do sind in diesem Fall gültig)

Der Schlüssel hier ist, dass Schlüsselwörter / reservierte Wörter eine besondere Bedeutung haben, da sie die Shell-Syntax erleichtern, dazu dienen, bestimmte Codeblöcke wie Schleifen, zusammengesetzte Befehle, Verzweigungsanweisungen (if / case) usw. zu signalisieren. Sie ermöglichen jedoch die Bildung von Befehlsanweisungen selbst - tut nichts, und in der Tat , wenn Sie eingeben Schlüsselwörter wie for, until, case- die Hülle eine vollständige Erklärung erwarten, andernfalls - Syntaxfehler:

$ for
bash: syntax error near unexpected token `newline'
$  

Auf Quellcode-Ebene werden die reservierten Wörter für bash in parese.y definiert , während den eingebauten Wörtern das gesamte Verzeichnis zugeordnet ist.

Randnotiz

Der GNU-Index wird [als reserviertes Wort angezeigt, es handelt sich jedoch um einen eingebauten Befehl. [[dagegen ist ein reserviertes Wort.

Siehe auch: Unterschiede zwischen Schlüsselwort, reserviertem Wort und eingebautem?

Sergiy Kolodyazhnyy
quelle