Powłoka jest interfejsem systemu operacyjnego. Zwykle jest to mniej lub bardziej niezawodny język programowania sam w sobie, ale z funkcjami zaprojektowanymi tak, aby ułatwić interakcję z systemem operacyjnym i systemem plików. Semantyka powłoki POSIX (dalej nazywana po prostu "powłoką") jest trochę zmienna, łącząc niektóre cechy LISP (wyrażenia s mają wiele wspólnego z dzieleniem słów powłoki ) i C (większość składni arytmetycznej powłoki semantyka pochodzi z C).
Drugi rdzeń składni powłoki pochodzi z jej wychowania jako pomieszania poszczególnych narzędzi UNIX. Większość elementów często wbudowanych w powłokę można w rzeczywistości zaimplementować jako polecenia zewnętrzne. Rzuca wielu neofitów powłoki w pętlę, gdy zdają sobie sprawę, że /bin/[
istnieje w wielu systemach.
$ if '/bin/[' -f '/bin/['; then echo t; fi
t
wat?
Ma to o wiele więcej sensu, jeśli spojrzysz na sposób implementacji powłoki. Oto implementacja, którą wykonałem jako ćwiczenie. Jest w Pythonie, ale mam nadzieję, że to nie jest problem dla nikogo. Nie jest strasznie wytrzymały, ale jest pouczający:
#!/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:
os.execl(args[0], *args)
else:
os.waitpid(cpid, 0)
if __name__ == '__main__':
main()
Mam nadzieję, że z powyższego wynika, że model wykonania powłoki to prawie:
1. Expand words.
2. Assume the first word is a command.
3. Execute that command with the following words as arguments.
Rozbudowa, rozdzielczość poleceń, wykonanie. Cała semantyka powłoki jest związana z jedną z tych trzech rzeczy, chociaż są one znacznie bogatsze niż implementacja, którą napisałem powyżej.
Nie wszystkie polecenia fork
. W rzeczywistości istnieje kilka poleceń, które nie mają zbyt wiele sensu zaimplementowanych jako zewnętrzne (takie, że musiałyby fork
), ale nawet te często są dostępne jako zewnętrzne w celu zapewnienia ścisłej zgodności z POSIX.
Bash rozwija się na tej podstawie, dodając nowe funkcje i słowa kluczowe w celu ulepszenia powłoki POSIX. Jest prawie kompatybilny z sh, a bash jest tak wszechobecny, że niektórzy autorzy skryptów przez lata nie zdają sobie sprawy, że skrypt może w rzeczywistości nie działać w systemie ściśle POSIX-owym. (Zastanawiam się też, jak ludzie mogą tak bardzo przejmować się semantyką i stylem jednego języka programowania, a tak mało semantyce i stylowi powłoki, ale ja się różnią).
Kolejność wyceny
To trochę podchwytliwe pytanie: Bash interpretuje wyrażenia w swojej podstawowej składni od lewej do prawej, ale w składni arytmetycznej ma pierwszeństwo C. Wyrażenia różnią się jednak od rozszerzeń . Z EXPANSION
sekcji podręcznika bash:
Kolejność rozwinięć jest następująca: rozwijanie nawiasów; interpretacja tyldy, interpretacja parametrów i zmiennych, interpretacja wyrażeń arytmetycznych i podstawianie poleceń (wykonywane w sposób od lewej do prawej); podział na słowa; i rozwijanie nazw plików.
Jeśli rozumiesz dzielenie słów, rozwijanie nazw plików i rozwijanie parametrów, jesteś na dobrej drodze do zrozumienia większości tego, co robi bash. Zauważ, że rozwijanie nazw plików po rozdzieleniu słów jest krytyczne, ponieważ zapewnia, że plik z białymi znakami w nazwie może być nadal dopasowany przez glob. Dlatego ogólnie dobre użycie rozszerzeń glob jest lepsze niż analizowanie poleceń .
Zakres
Zakres funkcji
Podobnie jak stary ECMAscript, powłoka ma zakres dynamiczny, chyba że jawnie zadeklarujesz nazwy w funkcji.
$ foo() { echo $x; }
$ bar() { local x; echo $x; }
$ foo
$ bar
$ x=123
$ foo
123
$ bar
$ …
Środowisko i „zakres” procesu
Podpowłoki dziedziczą zmienne swoich powłok nadrzędnych, ale inne rodzaje procesów nie dziedziczą niewyeksportowanych nazw.
$ x=123
$ ( echo $x )
123
$ bash -c 'echo $x'
$ export x
$ bash -c 'echo $x'
123
$ y=123 bash -c 'echo $y'
123
Możesz łączyć te zasady określania zakresu:
$ foo() {
> local -x bar=123
> bash -c 'echo $bar'
> }
$ foo
123
$ echo $bar
$
Dyscyplina typowania
Typy. Tak. Bash tak naprawdę nie ma typów i wszystko rozwija się do łańcucha (a może słowo byłoby bardziej odpowiednie). Ale przyjrzyjmy się różnym typom rozszerzeń.
Smyczki
Prawie wszystko można traktować jako ciąg. Barewords w bash to ciągi znaków, których znaczenie zależy całkowicie od zastosowanego do nich rozszerzenia.
Brak ekspansji
Warto wykazać, że nagie słowo jest tak naprawdę tylko słowem, a cytaty nic w tym nie zmieniają.
$ echo foo
foo
$ 'echo' foo
foo
$ "echo" foo
foo
Rozwinięcie podłańcucha
$ fail='echoes'
$ set -x
$ "${fail:0:-2}" Hello World
+ echo Hello World
Hello World
Więcej informacji na temat rozszerzeń można znaleźć w Parameter Expansion
sekcji podręcznika. Jest dość potężny.
Liczby całkowite i wyrażenia arytmetyczne
Możesz nasycić nazwy atrybutem liczby całkowitej, aby powiedzieć powłoce, aby traktowała prawą stronę wyrażeń przypisania jako arytmetykę. Następnie, gdy parametr się rozwinie, zostanie obliczony jako liczba całkowita przed rozwinięciem do… ciągu.
$ foo=10+10
$ echo $foo
10+10
$ declare -i foo
$ foo=$foo
$ echo $foo
20
$ echo "${foo:0:1}"
2
Tablice
Argumenty i parametry pozycyjne
Zanim porozmawiamy o tablicach, warto omówić parametry pozycyjne. Argumenty do skryptu powłoki można uzyskać za pomocą ponumerowanych parametrów, $1
, $2
, $3
, itd. Aby uzyskać dostęp do wszystkich tych parametrów jednocześnie za pomocą "$@"
, których ekspansja ma wiele wspólnego z tablic. Można ustawić i zmienić parametry pozycyjne uzywajac set
lub shift
poleceń wbudowanych, lub po prostu przez wywołanie powłoki lub funkcji powłoki z tymi parametrami:
$ 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
Podręcznik bash czasami odnosi się również do $0
parametru pozycyjnego. Uważam to za mylące, ponieważ nie uwzględnia go w liczbie argumentów $#
, ale jest to numerowany parametr, więc ue. $0
to nazwa powłoki lub bieżącego skryptu powłoki.
Tablice
Składnia tablic jest wzorowana na parametrach pozycyjnych, więc myślenie o tablicach jako o nazwanym rodzaju „zewnętrznych parametrów pozycyjnych” jest zazwyczaj zdrowe. Tablice można deklarować przy użyciu następujących metod:
$ foo=( element0 element1 element2 )
$ bar[3]=element3
$ baz=( [12]=element12 [0]=element0 )
Możesz uzyskać dostęp do elementów tablicy według indeksu:
$ echo "${foo[1]}"
element1
Możesz ciąć tablice:
$ printf '"%s"\n' "${foo[@]:1}"
"element1"
"element2"
Jeśli traktujesz tablicę jako normalny parametr, otrzymasz indeks zerowy.
$ echo "$baz"
element0
$ echo "$bar"
$ …
Jeśli użyjesz cudzysłowów lub odwrotnych ukośników, aby zapobiec dzieleniu słów, tablica zachowa określone dzielenie słów:
$ foo=( 'elementa b c' 'd e f' )
$ echo "${#foo[@]}"
2
Główną różnicą między tablicami a parametrami pozycyjnymi są:
- Parametry pozycyjne nie są rzadkie. Jeśli
$12
jest ustawione, możesz być pewien, że $11
jest ustawione. (Może być ustawiony na pusty łańcuch, ale $#
nie będzie mniejszy niż 12.) Jeśli "${arr[12]}"
jest ustawiona, nie ma gwarancji, że "${arr[11]}"
zostanie ustawiona, a długość tablicy może wynosić zaledwie 1.
- Zerowy element tablicy jest jednoznacznie zerowym elementem tej tablicy. W parametrach pozycyjnych zerowy element nie jest pierwszym argumentem , ale nazwą powłoki lub skryptu powłoki.
- Do
shift
tablicy musisz wyciąć i przypisać ją ponownie, na przykład arr=( "${arr[@]:1}" )
. Możesz też to zrobić unset arr[0]
, ale wtedy pierwszy element będzie miał indeks 1.
- Tablice mogą być współdzielone niejawnie między funkcjami powłoki jako globalne, ale musisz jawnie przekazać parametry pozycyjne do funkcji powłoki, aby je zobaczyła.
Często wygodnie jest używać rozszerzeń nazw plików do tworzenia tablic nazw plików:
$ dirs=( */ )
Polecenia
Polecenia są kluczowe, ale są też omówione bardziej szczegółowo niż w instrukcji. Przeczytaj SHELL GRAMMAR
sekcję. Istnieją różne rodzaje poleceń:
- Proste polecenia (np.
$ startx
)
- Rurociągi (np.
$ yes | make config
) (Lol)
- Listy (np.
$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)
- Polecenia złożone (np.
$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)
- Koprocesy (złożone, bez przykładu)
- Funkcje (nazwane polecenie złożone, które można traktować jako proste polecenie)
Model wykonania
Model wykonania obejmuje oczywiście zarówno stertę, jak i stos. Jest to typowe dla wszystkich programów UNIX. Bash ma również stos wywołań funkcji powłoki, widoczny poprzez zagnieżdżone użycie caller
wbudowanego.
Bibliografia:
SHELL GRAMMAR
Rozdział podręcznika bash
- XCU Shell Command Language dokumentacja
- Bash Przewodnik na wiki GreyCat użytkownika.
- Zaawansowane programowanie w środowisku UNIX
Proszę o komentarze, jeśli chcesz, abym dalej się rozwijał w określonym kierunku.