W jaki sposób dopasowywane są podwójne cudzysłowy w bash (sparowane)?

15

Używam GNU bash 4.3.48. Rozważ następujące dwa polecenia, które różnią się tylko jednym znakiem dolara.

Polecenie 1:

echo "(echo " * ")"

Polecenie 2:

echo "$(echo " * ")"

Ich wyniki są odpowiednio

(echo  test.txt ppcg.sh )

i

 * 

Więc oczywiście w pierwszym przypadku *jest on globowany, co oznacza, że ​​pierwszy znak cudzysłowu idzie z drugim, tworząc parę, a trzeci i czwarty tworzą inną parę.

W drugim przypadku *znak nie jest globowany, a na wyjściu są dokładnie dwie dodatkowe spacje, jedna przed gwiazdką, a druga po niej, co oznacza, że ​​drugi znak cudzysłowu jest zgodny z trzecim, a pierwszy z czwartym.

Czy istnieją inne przypadki poza $()konstrukcją, że znaki cudzysłowu nie są dopasowane do następnego, ale są zagnieżdżone? Czy to zachowanie jest dobrze udokumentowane, a jeśli tak, to gdzie mogę znaleźć odpowiedni dokument?

Weijun Zhou
źródło
2
Powiązane: Bash cytuje nieskalowane przy podstawianiu poleceń .
G-Man mówi „Przywróć Monikę”
Po raz kolejny podkreślenie składni w UL SE jest błędne i mylące, choć winna jest za to piękna JS.
Weijun Zhou

Odpowiedzi:

14

Każda z konstrukcji zagnieżdżonych, które mogą być interpolowane wewnątrz łańcuchów, może zawierać w sobie dalsze łańcuchy: są one przetwarzane jak nowy skrypt, aż do znacznika zamykającego, a nawet mogą być zagnieżdżone na wielu poziomach głębokości. Wszystkie takty jednego z nich zaczyna się od $. Wszystkie są udokumentowane w połączeniu instrukcji Bash i specyfikacji języka poleceń powłoki POSIX.

Istnieje kilka przypadków tych konstrukcji:

  • Zastąpienie poleceń$( ... ) , jak już znalazłeś. POSIX określa to zachowanie :

    W $(command)formularzu wszystkie znaki następujące po otwartym nawiasie do pasującego nawiasu zamykającego stanowią polecenie. Do polecenia można użyć dowolnego poprawnego skryptu powłoki ...

    Cytaty są częścią prawidłowych skryptów powłoki, więc są dozwolone w ich normalnym znaczeniu.

  • Zastępowanie poleceń `również.
  • Element „word” zaawansowanych instancji podstawiania parametrów, takich jak${parameter:-word} . Definicja „słowo” jest :

    Sekwencja znaków traktowana przez powłokę jako jednostka

    - który zawiera cytowany tekst, a nawet mieszane cytaty a"b"c'd'e- chociaż rzeczywiste zachowanie rozszerzeń jest nieco bardziej liberalne i na przykład ${x:-hello world}działa również.

  • Rozwinięcie arytmetyczne z $(( ... )), chociaż tam jest w dużej mierze bezużyteczne (ale można zagnieżdżać podstawianie poleceń lub rozszerzenia zmiennych, a następnie użyteczne w nich cudzysłowy). POSIX stwierdza, że :

    Wyrażenie traktuje się tak, jakby było w cudzysłowie, z tym wyjątkiem, że podwójny cudzysłów w wyrażeniu nie jest traktowany specjalnie. Powłoka powinna rozwinąć wszystkie tokeny w wyrażeniu w celu rozszerzenia parametrów, podstawiania poleceń i usuwania cytatów.

    więc to zachowanie jest wyraźnie wymagane. Oznacza to echo "abc $((4 "*" 5))"raczej arytmetykę niż globowanie.

    Zauważ jednak, że interpretacja $[ ... ]arytmetyczna w starym stylu nie jest traktowana w ten sam sposób: cudzysłowy będą błędem, jeśli się pojawią, niezależnie od tego, czy rozwinięcie jest cytowane, czy nie. Ten formularz nie jest już w ogóle dokumentowany i i tak nie jest przeznaczony do użycia.

  • Tłumaczenie specyficzne dla ustawień narodowych za pomocą$"..." , które faktycznie używa "jako podstawowego elementu. $"jest traktowany jako jedna jednostka.

Jest jeszcze jeden przypadek zagnieżdżenia, którego można się nie spodziewać, nie obejmujący cudzysłowów, który dotyczy rozszerzenia nawiasów : {a,b{c,d},e}rozwija się do „a bc bd e”.${x:-a{b,c}d}robi nie gniazdo, jednak; jest traktowane jako podstawienie parametru, podając „ a{b,c”, a następnie „ d}”. Jest to również udokumentowane :

Gdy używane są nawiasy klamrowe, pasujący nawias końcowy jest pierwszym znakiem „}”, który nie jest poprzedzany ukośnikiem odwrotnym ani w cudzysłowie, ani w osadzonym rozszerzeniu arytmetycznym, podstawianiu poleceń lub rozszerzaniu parametrów.


Zasadniczo wszystkie konstrukcje rozdzielane parsują swoje ciała niezależnie od otaczającego kontekstu (i wyjątki są traktowane jak błędy ). Zasadniczo po zobaczeniu $(kodu zastępującego polecenie prosi analizator składni, aby zużył tyle, ile może z ciała, jakby to był nowy program, a następnie sprawdza, czy oczekiwany znacznik kończący (nieskalowany )lub ))lub }) pojawia się po uruchomieniu podparsera z rzeczy, które może konsumować.

Jeśli myślisz o działaniu parsera rekurencyjno-opadającego , jest to zwykła rekurencja do przypadku podstawowego. W rzeczywistości jest to łatwiejsze niż w inny sposób, gdy w ogóle masz interpolację łańcuchów. Bez względu na podstawową technikę analizowania, powłoki obsługujące te konstrukty dają ten sam wynik.

Możesz zagnieżdżać cytaty tak głęboko, jak chcesz w tych konstrukcjach i będzie działać zgodnie z oczekiwaniami. Nigdzie nie pomylić, widząc cytat na środku; zamiast tego będzie to początek nowego cytowanego ciągu w kontekście wewnętrznym.

Michael Homer
źródło
Dzięki. W "blah/blah\n$(cat "${tmpdir}/${filename}.jpdf")": dlaczego drugi podwójny cudzysłów nie jest końcem pierwszego podwójnego cudzysłowu (jak pokazuje składnia podświetlenia w odpowiedzi), ale jest początkiem łańcucha $(...)? Czy to dlatego, że parser bash jest odgórny, a nie oddolny?
Tim
2
Istnieje wiele różnic w obsłudze "${var-"foo"}"( echo "${-+"*"}"na przykład takich samych jak echo *w powłoce Bourne'a lub Korna), a zachowanie zostanie wyraźnie określone w następnej wersji standardu . Zobacz także dyskusję na mail-archive.com/[email protected]/msg00167.html
Stéphane Chazelas
3

Być może spojrzenie na dwa przykłady z printf(zamiast echo) pomoże:

$ printf '<%s> ' "(echo " * ")"; echo
<(echo > <test.txt> <ppcg.sh> <file1> <file2> <file3> <)>

Drukuje (echo (pierwsze słowo, w tym spację końcową), niektóre pliki i słowo zamykające ).
Nawias jest tylko częścią cytowanego ciągu (echo .
Gwiazdka (teraz niecytowana, ponieważ dwa podwójne cudzysłowy są sparowane) jest rozszerzana jako glob do listy pasujących plików.
A potem nawias zamykający.

Jednak drugie polecenie działa w następujący sposób:

$ printf '<%s> ' "$(echo " * ")" ; echo
< * >

$Rozpoczyna podstawienia komendy. To zaczyna od nowa cytat.
Gwiazdka jest cytowana " * "i to właśnie wydaje polecenie (tutaj jest to polecenie, a nie ciąg cytowany) echo. Na koniec printfponownie sformatuj *i wydrukuje go jako < * >.

Izaak
źródło