Eine Shell ist eine Schnittstelle für das Betriebssystem. Es ist normalerweise eine mehr oder weniger robuste Programmiersprache für sich, aber mit Funktionen, die es einfach machen, spezifisch mit dem Betriebssystem und dem Dateisystem zu interagieren. Die Semantik der POSIX-Shell (im Folgenden nur als "Shell" bezeichnet) ist ein bisschen wie ein Köter, der einige Funktionen von LISP (S-Ausdrücke haben viel mit der Aufteilung von Shell- Wörtern gemeinsam ) und C (ein Großteil der arithmetischen Syntax der Shell) kombiniert Semantik kommt von C).
Die andere Wurzel der Syntax der Shell liegt in ihrer Erziehung als Mischmasch einzelner UNIX-Dienstprogramme. Die meisten der häufig in der Shell integrierten Funktionen können tatsächlich als externe Befehle implementiert werden. Es wirft viele Shell-Neophyten für eine Schleife, wenn sie erkennen, dass dies /bin/[
auf vielen Systemen vorhanden ist.
$ if '/bin/[' -f '/bin/['; then echo t; fi # Tested as-is on OS X, without the `]`
t
wat?
Dies ist viel sinnvoller, wenn Sie sich ansehen, wie eine Shell implementiert ist. Hier ist eine Implementierung, die ich als Übung gemacht habe. Es ist in Python, aber ich hoffe, das ist für niemanden ein Auflegen. Es ist nicht besonders robust, aber lehrreich:
#!/usr/bin/env python
from __future__ import print_function
import os, sys
'''Hacky barebones shell.'''
try:
input=raw_input
except NameError:
pass
def main():
while True:
cmd = input('prompt> ')
args = cmd.split()
if not args:
continue
cpid = os.fork()
if cpid == 0:
# We're in a child process
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
Ich hoffe, dass das Obige deutlich macht, dass das Ausführungsmodell einer Shell ziemlich genau ist:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Erweiterung, Befehlsauflösung, Ausführung. Die gesamte Semantik der Shell hängt mit einem dieser drei Dinge zusammen, obwohl sie weitaus umfangreicher sind als die Implementierung, die ich oben geschrieben habe.
Nicht alle Befehle fork
. Tatsächlich gibt es eine Handvoll Befehle, die als extern nicht sinnvoll sinnvoll sind (so dass sie es müssten)fork
), aber selbst diese sind häufig als externe für die strikte POSIX-Konformität verfügbar.
Bash baut auf dieser Basis auf und fügt neue Funktionen und Schlüsselwörter hinzu, um die POSIX-Shell zu verbessern. Es ist nahezu kompatibel mit sh und bash ist so allgegenwärtig, dass einige Skriptautoren Jahre damit verbringen, ohne zu bemerken, dass ein Skript möglicherweise nicht auf einem POSIX-strengen System funktioniert. (Ich frage mich auch, wie die Leute sich so sehr um die Semantik und den Stil einer Programmiersprache kümmern können und so wenig um die Semantik und den Stil der Shell, aber ich bin anderer Meinung.)
Reihenfolge der Bewertung
Dies ist eine Trickfrage: Bash interpretiert Ausdrücke in seiner primären Syntax von links nach rechts, aber in seiner arithmetischen Syntax folgt sie der C-Priorität. Ausdrücke unterscheiden sich jedoch von Erweiterungen . Aus dem EXPANSION
Abschnitt des Bash-Handbuchs:
Die Reihenfolge der Erweiterungen lautet: Klammererweiterung; Tilde-Erweiterung, Parameter- und Variablenerweiterung, arithmetische Erweiterung und Befehlssubstitution (von links nach rechts); Wortteilung; und Pfadnamenerweiterung.
Wenn Sie Wortsplitting, Pfadnamenerweiterung und Parametererweiterung verstehen, sind Sie auf dem besten Weg, die meisten Funktionen von Bash zu verstehen. Beachten Sie, dass die Pfadnamenerweiterung nach dem Aufteilen von Wörtern von entscheidender Bedeutung ist, da dadurch sichergestellt wird, dass eine Datei mit einem Leerzeichen im Namen weiterhin von einem Glob abgeglichen werden kann. Aus diesem Grund ist die gute Verwendung von Glob-Erweiterungen besser als das Parsen von Befehlen im Allgemeinen .
Umfang
Funktionsumfang
Ähnlich wie altes ECMAscript hat die Shell einen dynamischen Bereich, es sei denn, Sie deklarieren explizit Namen innerhalb einer Funktion.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Umwelt und Prozess "Umfang"
Subshells erben die Variablen ihrer übergeordneten Shells, andere Arten von Prozessen erben jedoch keine nicht exportierten Namen.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y' # another way to transiently export a name
123
Sie können diese Bereichsregeln kombinieren:
$ foo() {
> local -x bar=123 # Export foo, but only in this scope
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Schreibdisziplin
Ähm, Typen. Ja. Bash hat wirklich keine Typen und alles wird zu einer Zeichenfolge erweitert (oder vielleicht wäre ein Wort besser geeignet). Aber lassen Sie uns die verschiedenen Arten von Erweiterungen untersuchen.
Saiten
So ziemlich alles kann als String behandelt werden. Barwörter in Bash sind Zeichenfolgen, deren Bedeutung vollständig von der darauf angewendeten Erweiterung abhängt.
Keine Erweiterung
Es kann sich lohnen zu zeigen, dass ein bloßes Wort wirklich nur ein Wort ist und dass Zitate daran nichts ändern.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Teilstringerweiterung
$ fail='echoes'
$ set -x # So we can see what's going on
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Weitere Informationen zu Erweiterungen finden Sie im Parameter Expansion
Abschnitt des Handbuchs. Es ist ziemlich mächtig.
Ganzzahlen und arithmetische Ausdrücke
Sie können Namen mit dem Integer-Attribut versehen, um die Shell anzuweisen, die rechte Seite von Zuweisungsausdrücken als arithmetisch zu behandeln. Wenn der Parameter dann erweitert wird, wird er als ganzzahlige Mathematik ausgewertet, bevor er zu… einem String erweitert wird.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo # Must re-evaluate the assignment
$ echo $foo
20
$ echo "${foo:0:1}" # Still just a string
2
Arrays
Argumente und Positionsparameter
Bevor Sie über Arrays sprechen, sollten Sie die Positionsparameter diskutieren. Die Argumente für ein Shell - Skript kann mit nummerierten Parameter, die zugegriffen werden soll $1
, $2
, $3
, etc. Sie alle diese Parameter auf einmal zugreifen können "$@"
, die Erweiterung viele Dinge gemeinsam mit Arrays hat. Sie können die Positionsparameter mithilfe der set
oder integrierten shift
Funktionen oder einfach durch Aufrufen der Shell oder einer Shell-Funktion mit den folgenden Parametern festlegen und ändern :
$ bash -c 'for ((i=1;i<=$#;i++)); do
> printf "\$%d => %s\n" "$i" "${@:i:1}"
> done' -- foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showpp() {
> local i
> for ((i=1;i<=$#;i++)); do
> printf '$%d => %s\n' "$i" "${@:i:1}"
> done
> }
$ showpp foo bar baz
$1 => foo
$2 => bar
$3 => baz
$ showshift() {
> shift 3
> showpp "$@"
> }
$ showshift foo bar baz biz quux xyzzy
$1 => biz
$2 => quux
$3 => xyzzy
Das Bash-Handbuch wird manchmal auch $0
als Positionsparameter bezeichnet. Ich finde das verwirrend, weil es es nicht in die Argumentanzahl einbezieht $#
, aber es ist ein nummerierter Parameter, also meh. $0
ist der Name der Shell oder des aktuellen Shell-Skripts.
Arrays
Die Syntax von Arrays wird nach Positionsparametern modelliert, daher ist es meistens sinnvoll, sich Arrays als benannte Art von "externen Positionsparametern" vorzustellen, wenn Sie möchten. Arrays können mit den folgenden Ansätzen deklariert werden:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Sie können über den Index auf Array-Elemente zugreifen:
$ echo "${foo[1]}"
element1
Sie können Arrays in Scheiben schneiden:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
Wenn Sie ein Array als normalen Parameter behandeln, erhalten Sie den nullten Index.
$ echo "$baz"
element0
$ echo "$bar" # Even if the zeroth index isn't set
$ …
Wenn Sie Anführungszeichen oder Backslashes verwenden, um das Aufteilen von Wörtern zu verhindern, behält das Array das angegebene Aufteilen von Wörtern bei:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Der Hauptunterschied zwischen Arrays und Positionsparametern ist:
- Positionsparameter sind nicht spärlich. Wenn
$12
eingestellt ist, können Sie sicher sein, dass auch $11
eingestellt ist. (Es könnte auf die leere Zeichenfolge gesetzt werden, wird aber $#
nicht kleiner als 12 sein.) Wenn "${arr[12]}"
gesetzt ist, gibt es keine Garantie, "${arr[11]}"
die gesetzt ist, und die Länge des Arrays könnte so klein wie 1 sein.
- Das nullte Element eines Arrays ist eindeutig das nullte Element dieses Arrays. In Positionsparametern ist das nullte Element nicht das erste Argument , sondern der Name der Shell oder des Shell-Skripts.
- Zu
shift
einem Array müssen Sie es in Scheiben schneiden und neu zuweisen, wie z arr=( "${arr[@]:1}" )
. Sie könnten es auch tun unset arr[0]
, aber das würde das erste Element bei Index 1 ergeben.
- Arrays können implizit von Shell-Funktionen als Globals gemeinsam genutzt werden. Sie müssen jedoch Positionsparameter explizit an eine Shell-Funktion übergeben, damit diese angezeigt werden.
Es ist oft praktisch, Pfadnamenerweiterungen zu verwenden, um Arrays von Dateinamen zu erstellen:
$ dirs=( */ )
Befehle
Befehle sind der Schlüssel, aber sie werden auch ausführlicher behandelt, als ich es im Handbuch kann. Lesen Sie den SHELL GRAMMAR
Abschnitt. Die verschiedenen Arten von Befehlen sind:
- Einfache Befehle (zB
$ startx
)
- Pipelines (zB
$ yes | make config
) (lol)
- Listen (zB
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- Verbindung Befehle (zB
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- Coprozesse (komplex, kein Beispiel)
- Funktionen (Ein benannter zusammengesetzter Befehl, der als einfacher Befehl behandelt werden kann)
Ausführungsmodell
Das Ausführungsmodell umfasst natürlich sowohl einen Heap als auch einen Stack. Dies ist in allen UNIX-Programmen endemisch. Bash hat auch einen Aufrufstapel für Shell-Funktionen, der über die verschachtelte Verwendung von sichtbar istcaller
Systems .
Verweise:
- Der
SHELL GRAMMAR
Abschnitt des Bash-Handbuchs
- Die Dokumentation zur XCU Shell Command Language
- Der Bash Guide im Greycat-Wiki.
- Erweiterte Programmierung in der UNIX-Umgebung
Bitte machen Sie Kommentare, wenn Sie möchten, dass ich in eine bestimmte Richtung weiter expandiere.