Jaka jest różnica między wbudowaną powłoką a słowem kluczowym powłoki?

33

Kiedy uruchamiam te dwa polecenia, otrzymuję

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

Jest wyraźnie pokazane, że cdjest wbudowaną powłoką i ifjest słowem kluczowym powłoki. Jaka jest różnica między wbudowaną powłoką a słowem kluczowym?

Avinash Raj
źródło
2
powiązane: unix.stackexchange.com/questions/267761/...
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Odpowiedzi:

45

Istnieje duża różnica między wbudowanym a słowem kluczowym w sposobie, w jaki Bash analizuje kod. Zanim porozmawiamy o różnicy, wypiszmy wszystkie słowa kluczowe i wbudowane:

Wbudowane:

$ 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                          

Słowa kluczowe:

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

Zauważ, że na przykład [jest wbudowany i to [[jest słowo kluczowe. Użyję tych dwóch, aby zilustrować różnicę poniżej, ponieważ są one dobrze znanymi operatorami: wszyscy je znają i używają ich regularnie (lub powinni).

Słowo kluczowe jest skanowane i rozumiane przez Bash na bardzo wczesnym etapie jego analizy. Umożliwia to na przykład:

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

To działa dobrze, a Bash z przyjemnością wyświetli

The string is non-empty

Pamiętaj, że nie cytowałem $string_with_spaces. Mając na uwadze, że:

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

pokazuje, że Bash nie jest szczęśliwy:

bash: [: too many arguments

Dlaczego działa ze słowami kluczowymi, a nie z wbudowanymi? ponieważ kiedy Bash analizuje kod, widzi, [[które jest słowem kluczowym i bardzo wcześnie rozumie, że jest wyjątkowy. Będzie więc szukał zamknięcia ]]i potraktuje wnętrze w specjalny sposób. Wbudowane (lub polecenie) jest traktowane jako rzeczywiste polecenie, które będzie wywoływane z argumentami. W tym ostatnim przykładzie bash rozumie, że powinien uruchomić polecenie [z argumentami (pokazane po jednym w wierszu):

-n
some
spaces
here
]

ponieważ następuje rozwijanie zmiennych, usuwanie cytatów, rozwijanie nazw ścieżek i dzielenie słów. Komenda [okazuje się być wbudowana w powłokę, więc wykonuje ją z tymi argumentami, co powoduje błąd, stąd skarga.

W praktyce widać, że to rozróżnienie pozwala na wyrafinowane zachowanie, które nie byłoby możliwe w przypadku wbudowanych poleceń (lub poleceń).

W praktyce, jak możesz odróżnić wbudowane narzędzie od słowa kluczowego? to zabawny eksperyment do wykonania:

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

Kiedy Bash analizuje linię $a -d . ], nie widzi nic specjalnego (tj. Żadnych aliasów, żadnych przekierowań, żadnych słów kluczowych), więc po prostu wykonuje zmienną interpretację. Po zmiennych rozszerzeniach widzi:

[ -d . ]

tak wykonuje polecenia (wbudowane) [z argumentami -d, .i ], co oczywiście jest prawdą (to tylko testy czy .jest katalogiem).

Nowy wygląd:

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

O. Dzieje się tak, ponieważ gdy Bash widzi tę linię, nie widzi nic specjalnego, a zatem rozwija wszystkie zmienne i ostatecznie widzi:

[[ -d . ]]

W tej chwili rozszerzenia aliasów i skanowanie słów kluczowych były już od dawna wykonywane i nie będą już wykonywane, więc Bash próbuje znaleźć polecenie o nazwie [[, nie znajduje go i narzeka.

Wzdłuż tych samych linii:

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

i

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

Rozszerzenie aliasu jest również czymś wyjątkowym. Wszyscy przynajmniej raz wykonaliście następujące czynności:

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

Rozumowanie jest takie samo: rozwinięcie aliasu następuje na długo przed rozwinięciem zmiennej i usunięciem cytatu.


Słowo kluczowe a Alias

Jak myślisz, co się stanie, jeśli zdefiniujemy alias jako słowo kluczowe?

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

Och, to działa! więc aliasy mogą być używane do aliasu słów kluczowych! dobrze wiedzieć.


Wniosek: wbudowane naprawdę zachowują się jak polecenia: odpowiadają one wykonywanej akcji z argumentami, które podlegają bezpośredniemu rozszerzaniu zmiennych oraz dzieleniu i dzieleniu słów. To naprawdę tak, jakby mieć gdzieś zewnętrzną komendę /binlub /usr/binjest ona wywoływana z argumentami podanymi po rozwinięciu zmiennej itp. Zauważ, że kiedy mówię, to tak naprawdę, jak z zewnętrzną komendą, mam na myśli tylko w odniesieniu do argumentów, dzielenia słów, globowania, zmienna ekspansja itp. Wbudowany może modyfikować wewnętrzny stan powłoki!

Z drugiej strony słowa kluczowe są skanowane i rozumiane bardzo wcześnie i umożliwiają wyrafinowane zachowanie powłoki: powłoka będzie mogła zabronić dzielenia słów lub rozwijania nazw ścieżek itp.

Teraz spójrz na listę wbudowanych i słów kluczowych i spróbuj dowiedzieć się, dlaczego niektóre muszą być słowami kluczowymi.


!jest słowem kluczowym. Wydaje się, że można by naśladować jego zachowanie za pomocą funkcji:

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

ale to zabraniałoby konstrukcji takich jak:

$ ! ! true
$ echo $?
0

lub

$ ! { true; }
echo $?
1

To samo dla time: bardziej wydajne jest użycie słowa kluczowego, aby mógł on mierzyć złożone polecenia złożone i potoki z przekierowaniami:

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

Gdyby timezwykłe polecenie (nawet wbudowane), widziałoby tylko argumenty grep, ^#a /home/gniourf/.bashrctym razem, a następnie jego dane wyjściowe przechodziłyby przez pozostałe części potoku. Ale dzięki słowu kluczowemu Bash poradzi sobie ze wszystkim! może timekompletny potok, w tym przekierowania! Gdyby to timebyło zwykłe polecenie, nie moglibyśmy zrobić:

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

Spróbuj:

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

Spróbuj naprawić (?) To:

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

Beznadziejny.


Słowo kluczowe a alias?

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

Jak myślisz, co się dzieje?


Naprawdę, wbudowane jest jak polecenie, tyle że jest wbudowane w powłokę, podczas gdy słowo kluczowe pozwala na wyrafinowane zachowanie! możemy powiedzieć, że jest to część gramatyki powłoki.

gniourf_gniourf
źródło
2
Uzgodniony z @JohnyTex, jest to jedna z najbardziej kompleksowych i odpowiednich odpowiedzi, jakie widziałem na stronach stosu. Dziękuję Ci. Jeden może niezwiązane pytanie: po prostu z ciekawości próbuję znaleźć w dokumentacji „alias” tymczasowo wyłączyć funkcjonalności z poprzedniego polecenia z =\"za pomocą man, apropos, i help, a ja nie miałem szczęścia. Masz pomysł, gdzie mógłbym znaleźć te informacje? Przede wszystkim dlatego, że w przyszłości mogę zobaczyć, co jeszcze tam jest, ponieważ myślę, że brakuje mi źródła odniesienia.
nc.
@ nc: nie znajdziesz go udokumentowane jawnie. Powód, dla którego działa, wyjaśniono w tej odpowiedzi. Najbliższe znajdziesz w podręczniku w dziale Operacja powłoki . Zobaczysz, że rozwijanie aliasu odbywa się bardzo wcześnie (coś, co starałem się podkreślić w tej odpowiedzi), w kroku 2. Usuwanie cytatów, rozwijanie parametrów, globowanie itp. Są wykonywane później. Tak aby wyłączyć aliasu, można użyć pewnego rodzaju powołując się zabronić muszlę od zrozumienia token jako alias, np \ll, "ll"albo 'll'.
gniourf_gniourf
1
Właściwie to dostałem zamówienie, aliasy i słowa kluczowe są uruchamiane jako pierwsze. Ponieważ cytujemy [[wydajność \[[, nie jest ona analizowana jako alias. Jak dotąd? Kiedy zgubiłem się, nie zdawałem sobie sprawy, że odwrotny ukośnik był cytatem w Bash, i spróbowałem to sprawdzić pod aliasami i słowami kluczowymi i całkowicie się zgubiłem ... Wszystko dobrze. W sekcji Cytowanie:> Niecytowany ukośnik odwrotny () to znak zmiany znaczenia.
nc.
1
Możemy więc pomyśleć o (2) na tej liście Op jako Tokenizacji i \[[jest to pojedynczy token typu LiteralQuoteToken, w przeciwieństwie do [[OpenTestKeywordToken, który wymaga zamykającego ]]CloseTestKeywordToken do kompilacji oroper / poprawnej składni. Później w (4) LiteralQuoteToken zostanie oceniony [[jako nazwa bulitin / polecenia do wykonania, aw (6) Bash zbankrutuje, ponieważ nie ma [[wbudowanego polecenia ani polecenia. Miły. Z czasem mogę zapomnieć o dokładnych szczegółach, ale w tej chwili sposób, w jaki Bash prowadzi rzeczy, jest dla mnie o wiele bardziej wyraźny; Dziękuję Ci.
nc.
9

man bashdzwoni do nich SHELL BUILTIN COMMANDS. Tak więc „wbudowana powłoka” jest jak zwykłe polecenie, jak grepitp., Ale zamiast być zawarta w osobnym pliku, jest wbudowana w samą bash . Dzięki temu działają one wydajniej niż polecenia zewnętrzne.

Słowo kluczowe jest również „zakodowane na stałe w Bash, ale w przeciwieństwie do wbudowanego słowa kluczowego nie jest samo w sobie poleceniem, ale podjednostką konstrukcji polecenia”. Interpretuję to w ten sposób, że słowa kluczowe nie pełnią żadnej funkcji, ale wymagają poleceń do wykonania dowolnej czynności. (Z linku, inne przykłady for, while, do, i !, i są bardziej w mojej odpowiedzi na inne pytanie.)

Krogulec
źródło
1
Ciekawy fakt: [[ is a shell keywordale [ is a shell builtin. Nie mam pojęcia dlaczego.
Sparhawk
1
Prawdopodobnie ze względów historycznych i standardu POSIX jest on jak najbardziej zgodny ze starą powłoką Bourne'a, ponieważ [istniał jako osobne polecenie w przeszłości. [[nie jest określony przez standard, więc programiści mogą zdecydować się na uwzględnienie go jako słowa kluczowego lub jako wbudowanego.
Sergiy Kolodyazhnyy
1

Podręcznik wiersza poleceń dostarczany z Ubuntu nie zawiera definicji słów kluczowych, jednak podręcznik online (patrz sidenote) i standardowe specyfikacje POSIX Shell Command Language określają je jako „słowa zastrzeżone” i oba zawierają ich listę. Ze standardu POSIX:

Rozpoznanie to nastąpi tylko wtedy, gdy żaden z tych znaków nie jest cytowany i gdy słowo jest użyte jako:

  • Pierwsze słowo polecenia

  • Pierwsze słowo następujące po jednym z zastrzeżonych słów innych niż case, for lub in

  • Trzecie słowo w poleceniu case (tylko w tym przypadku jest poprawne)

  • Trzecie słowo w poleceniu a (w tym przypadku poprawne są tylko i do)

Kluczem tutaj jest to, że słowa kluczowe / słowa zastrzeżone mają specjalne znaczenie, ponieważ ułatwiają składnię powłoki, służą do sygnalizowania pewnych bloków kodu, takich jak pętle, polecenia złożone, instrukcje rozgałęziające (if / case) itp. Pozwalają one tworzyć instrukcje, ale sami - nie rób nic, aw rzeczywistości po wpisaniu słów kluczowych, takich jak for, until, case- powłoka będzie oczekiwać kompletne oświadczenie, w przeciwnym razie - błąd składni:

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

Na poziomie kodu źródłowego słowa zastrzeżone dla bash są zdefiniowane w parese.y , podczas gdy wbudowane mają dedykowany im cały katalog .

Dygresja

Indeks GNU pokazuje się [jako słowo zastrzeżone, jednak w rzeczywistości jest to wbudowane polecenie. [[natomiast jest słowem zastrzeżonym.

Zobacz także: Różnice między słowem kluczowym, słowem zastrzeżonym i wbudowanym?

Sergiy Kolodyazhnyy
źródło