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 /bin
oder /usr/bin
dass 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 time
nur ein Befehl (auch eingebaut) vorhanden wäre, würden nur die Argumente angezeigt grep
, ^#
und zu gegebener /home/gniourf/.bashrc
Zeit würde seine Ausgabe die verbleibenden Teile der Pipeline durchlaufen. Aber mit einem Schlüsselwort kann Bash alles bewältigen! es kann time
die komplette pipeline inklusive der umleitungen! Wenn time
es 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.