Bardziej niż w jakimkolwiek innym języku, który znam, „nauczyłem się” Bash przez Google za każdym razem, gdy potrzebuję jakiejś małej rzeczy. W związku z tym mogę łączyć ze sobą małe skrypty, które wydają się działać. Jednak tak naprawdę nie wiem, co się dzieje i liczyłem na bardziej formalne wprowadzenie do Bash jako języka programowania. Na przykład: Jaka jest kolejność oceny? jakie są zasady określania zakresu? Jaka jest dyscyplina pisania, np. Czy wszystko jest ciągiem znaków? Jaki jest stan programu - czy jest to przypisanie ciągów znaków do nazw zmiennych; czy jest coś więcej, np. stos? Czy jest kupa? I tak dalej.
Pomyślałem, że poszuka tego w podręczniku GNU Bash, ale nie wydaje mi się, żeby to było to, czego chcę; jest to raczej lista do prania cukru syntaktycznego niż wyjaśnienie podstawowego modelu semantycznego. Milion i jeden „samouczków bash” online jest tylko gorszy. Może powinienem najpierw przestudiować sh
i zrozumieć Bash jako dodatek do cukru syntaktycznego? Nie wiem jednak, czy to dokładny model.
Jakieś sugestie?
EDYCJA: Poproszono mnie o podanie przykładów tego, czego idealnie szukam. Dość skrajnym przykładem tego, co uważam za „semantykę formalną”, jest ten artykuł na temat „istoty JavaScript” . Być może nieco mniej formalnym przykładem jest raport Haskell 2010 .
źródło
Odpowiedzi:
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 # Tested as-is on OS X, without the `]` 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: # We're in a child process 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łybyfork
), 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: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' # another way to transiently export a name 123
Możesz łączyć te zasady określania zakresu:
$ foo() { > local -x bar=123 # Export foo, but only in this scope > 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 ekspansjiWarto wykazać, że nagie słowo jest tak naprawdę tylko słowem, a cytaty nic w tym nie zmieniają.
Rozwinięcie podłańcucha$ echo foo foo $ 'echo' foo foo $ "echo" foo foo
$ fail='echoes' $ set -x # So we can see what's going on $ "${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 # Must re-evaluate the assignment $ echo $foo 20 $ echo "${foo:0:1}" # Still just a string 2
Tablice
Argumenty i parametry pozycyjneZanim 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 uzywajacset
lubshift
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
Tablice$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.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:
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" # Even if the zeroth index isn't set $ …
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ą:
$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.shift
tablicy musisz wyciąć i przypisać ją ponownie, na przykładarr=( "${arr[@]:1}" )
. Możesz też to zrobićunset arr[0]
, ale wtedy pierwszy element będzie miał indeks 1.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ń:$ startx
)$ yes | make config
) (Lol)$ grep -qF foo file && sed 's/foo/bar/' file > newfile
)$ ( cd -P /var/www/webroot && echo "webroot is $PWD" )
)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 bashProszę o komentarze, jeśli chcesz, abym dalej się rozwijał w określonym kierunku.
źródło
yes | make config
;-) Ale poważnie, bardzo dobry opis./bin/[
i/bin/test
często jest to ta sama aplikacja 2) „Załóżmy, że pierwsze słowo to polecenie”. - spodziewaj się, kiedy wykonasz zadanie ...execle
i wstawić pierwsze słowa do środowiska, ale to i tak uczyniłoby to nieco bardziej skomplikowanym.a = 1
nie pracują).Odpowiedź na Twoje pytanie „Na czym polega dyscyplina pisania, np. Czy wszystko jest ciągiem znaków” Zmienne Bash to ciągi znaków. Ale Bash zezwala na operacje arytmetyczne i porównania na zmiennych, gdy zmienne są liczbami całkowitymi. Wyjątkiem od reguły Bash zmienne są łańcuchy znaków, gdy te zmienne są złożone lub zadeklarowane w inny sposób
$ A=10/2 $ echo "A = $A" # Variable A acting like a String. A = 10/2 $ B=1 $ let B="$B+1" # Let is internal to bash. $ echo "B = $B" # One is added to B was Behaving as an integer. B = 2 $ A=1024 # A Defaults to string $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ echo "B = $B" # $B STRING is a string B = 10STRING01 $ B=${A/24/STRING01} # Substitute "24" with "STRING01". $ declare -i B $ echo "B = $B" # Declaring a variable with non-integers in it doesn't change the contents. B = 10STRING01 $ B=${B/STRING01/24} # Substitute "STRING01" with "24". $ echo "B = $B" B = 1024 $ declare -i B=10/2 # Declare B and assigning it an integer value $ echo "B = $B" # Variable B behaving as an Integer B = 5
Zadeklaruj znaczenie opcji:
źródło
Strona podręcznika basha zawiera trochę więcej informacji niż większość stron podręcznika, a także zawiera część tego, o co prosisz. Moje założenie po ponad dekadzie skryptowania bash jest takie, że ze względu na swoją historię jako rozszerzenie sh, ma on pewną funky składnię (aby zachować wsteczną kompatybilność z sh).
FWIW, moje doświadczenie było takie jak Twoje; chociaż różne książki (np. O'Reilly „Learning the Bash Shell” i podobne) pomagają w składni, istnieje wiele dziwnych sposobów rozwiązywania różnych problemów, a niektórych z nich nie ma w książce i należy je przeszukać.
źródło