Kiedy konieczne jest podwójne cytowanie?

120

Dawna rada polegała na podwójnym cytowaniu dowolnego wyrażenia obejmującego $VARIABLE, przynajmniej jeśli ktoś chciałby, aby był interpretowany przez powłokę jako pojedynczy element, w przeciwnym razie wszelkie spacje w treści $VARIABLEzrzucałyby powłokę.

Rozumiem jednak, że w nowszych wersjach powłok podwójne cytowanie nie jest już zawsze potrzebne (przynajmniej w celu opisanym powyżej). Na przykład w bash:

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

Z zshdrugiej strony te same trzy polecenia są skuteczne. Dlatego na podstawie tego eksperymentu wydaje się, że bashwewnątrz można pominąć podwójne cudzysłowy w środku [[ ... ]], ale nie wewnątrz [ ... ]ani w argumentach wiersza poleceń, podczas gdy zshwe wszystkich tych przypadkach można pominąć podwójne cudzysłowy.

Ale wnioskowanie ogólnych reguł z niepotwierdzonych przykładów takich jak powyższy jest przypadkową propozycją. Byłoby miło zobaczyć podsumowanie, kiedy konieczne jest podwójne cytowanie. Jestem zainteresowany przede wszystkim zsh, bashi /bin/sh.

kjo
źródło
10
Twoje obserwowane zachowanie w Zsh zależy od ustawień i zależy od SH_WORD_SPLITopcji.
Ulrich Dangel
3
Nawiasem mówiąc - nazwy zmiennych pisane wielkimi literami są używane przez zmienne mające znaczenie dla systemu operacyjnego i powłoki; specyfikacja POSIX wyraźnie zaleca używanie nazw małych liter dla zmiennych zdefiniowanych przez aplikację. (Podczas gdy cytowana specyfikacja koncentruje się w szczególności na zmiennych środowiskowych, zmienne środowiskowe i zmienne powłoki mają wspólną przestrzeń nazw: Próba utworzenia zmiennej powłoki o nazwie już używanej przez zmienną środowiskową zastępuje tę drugą). Zobacz pubs.opengroup.org/onlinepubs/009695399/basedefs/… , czwarty akapit.
Charles Duffy

Odpowiedzi:

128

Najpierw oddziel zsh od reszty. To nie jest kwestia starych czy współczesnych powłok: zsh zachowuje się inaczej. Projektanci zsh postanowili uczynić go niezgodnym z tradycyjnymi powłokami (Bourne, ksh, bash), ale łatwiejszym w użyciu.

Po drugie, o wiele łatwiej jest używać podwójnych cudzysłowów przez cały czas niż pamiętać, kiedy są potrzebne. Są one potrzebne przez większość czasu, więc musisz się uczyć, kiedy nie są potrzebne, a nie kiedy są potrzebne.

W skrócie, podwójne cudzysłowy są konieczne wszędzie tam, gdzie oczekiwana jest lista słów lub wzór . Są opcjonalne w kontekstach, w których analizator oczekuje surowego ciągu.

Co dzieje się bez cytatów

Zauważ, że bez podwójnych cudzysłowów zdarzają się dwie rzeczy.

  1. Po pierwsze, wynik interpretacji (wartość zmiennej dla podstawienia parametru jak ${foo}lub wynik polecenia dla podstawienia polecenia jak $(foo)) jest dzielony na słowa wszędzie tam, gdzie zawiera białe znaki.
    Dokładniej, wynik rozwinięcia jest dzielony na każdy znak, który pojawia się w wartości IFSzmiennej (znak separatora). Jeśli sekwencja znaków separatora zawiera spacje (spację, tabulator lub znak nowej linii), spacja jest liczona jako pojedynczy znak; wiodące, końcowe lub powtarzające się separatory niebiałe prowadzą do pustych pól. Na przykład, with IFS=" :", :one::two : three: :four produkuje puste pola przed one, pomiędzy onei twooraz (jeden) pomiędzy threei four.
  2. Każde pole powstałe w wyniku podziału jest interpretowane jako glob (wzór wieloznaczny), jeśli zawiera jeden ze znaków \[*?. Jeśli ten wzór pasuje do jednej lub więcej nazw plików, wzór jest zastępowany listą pasujących nazw plików.

Niecytowane rozwinięcie zmiennej $foojest potocznie nazywane „operatorem podziału + operatora globalnego”, w przeciwieństwie do tego, "$foo"który po prostu przyjmuje wartość zmiennej foo. To samo dotyczy podstawiania poleceń: "$(foo)"to podstawienie polecenia, $(foo)to zastąpienie polecenia, po którym następuje split + glob.

Gdzie można pominąć podwójne cudzysłowy

Oto wszystkie przypadki, o których mogę myśleć w powłoce w stylu Bourne'a, w której można wpisać zmienną lub podstawienie polecenia bez podwójnych cudzysłowów, a wartość jest interpretowana dosłownie.

  • Po prawej stronie zadania.

    var=$stuff
    a_single_star=*

    Zauważ, że potrzebujesz podwójnych cudzysłowów po export, ponieważ jest to zwykłe wbudowane, a nie słowo kluczowe. Jest to prawdą tylko w niektórych powłokach, takich jak dash, zsh (w emulacji sh), yash lub posh; bash i ksh traktują exportspecjalnie.

    export VAR="$stuff"
  • W caseoświadczeniu.

    case $var in 

    Zauważ, że potrzebujesz podwójnych cudzysłowów we wzorze przypadku. Podział słów nie występuje we wzorcu wielkości liter, ale zmienna niecytowana jest interpretowana jako wzorzec, podczas gdy zmienna cytowana jest interpretowana jako ciąg literału.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • W podwójnych nawiasach. Podwójne nawiasy kwadratowe są specjalną składnią powłoki.

    [[ -e $filename ]]

    Tyle, że potrzebujesz podwójnych cudzysłowów tam, gdzie oczekuje się wzorca lub wyrażenia regularnego: po prawej stronie =lub ==lub !=lub =~.

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi

    Potrzebujesz podwójnych cudzysłowów jak zwykle w pojedynczych nawiasach, [ … ]ponieważ są one zwykłą składnią powłoki (jest to polecenie, które się wywołuje [). Zobacz nawiasy pojedyncze lub podwójne

  • W przekierowaniu w nieinteraktywnych powłokach POSIX (nie bash, ani ksh88).

    echo "hello world" >$filename

    Niektóre powłoki, gdy są interaktywne, traktują wartość zmiennej jako wzór wieloznaczny. POSIX zabrania tego zachowania w nieinteraktywnych powłokach, ale kilka powłok, w tym bash (z wyjątkiem trybu POSIX) i ksh88 (w tym gdy jest to (rzekomo) POSIX shniektórych komercyjnych uniksów takich jak Solaris) nadal to bashrobi ( próbuje również podzielić i przekierowanie nie powiedzie się, chyba że rozłam + nazw plików wyników dokładnie jednego słowa), dlatego lepiej zacytować cele przekierowań w shskrypcie w przypadku, gdy chcesz przekonwertować go do bashskryptu niektóre dni, lub uruchomić go w systemie, w którym shjest niezgodny w tym punkcie lub może pochodzić z interaktywnych powłok.

  • Wewnątrz wyrażenia arytmetycznego. W rzeczywistości musisz pominąć cudzysłowy, aby zmienna była analizowana jako wyrażenie arytmetyczne.

    expr=2*2
    echo "$(($expr))"

    Jednak potrzebujesz cudzysłowów wokół rozszerzenia arytmetycznego, ponieważ są one podzielone na słowa w większości powłok, jak wymaga POSIX (!?).

  • W indeksie tablicy asocjacyjnej.

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

W niektórych rzadkich przypadkach przydatna może być niecytowana zmienna i podstawienie polecenia:

  • Gdy wartość zmiennej lub wynik polecenia składa się z listy wzorców globu, a chcesz rozwinąć te wzorce do listy pasujących plików.
  • Jeśli wiesz, że wartość nie zawiera znaku wieloznacznego, $IFSnie została ona zmodyfikowana i chcesz podzielić ją na białe znaki.
  • Jeśli chcesz podzielić wartość na określony znak: wyłącz globowanie za pomocą set -f, ustaw IFSznak separatora (lub zostaw to w spokoju, aby dzielił się spacją), a następnie wykonaj rozwinięcie.

Zsh

W Zsh można przeoczyć podwójne cudzysłowy przez większość czasu, z kilkoma wyjątkami.

  • $varnigdy nie rozwija się do wielu słów, jednak rozwija się do pustej listy (w przeciwieństwie do listy zawierającej pojedyncze, puste słowo), jeśli wartością varjest pusty ciąg. Kontrast:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    Podobnie "${array[@]}"rozwija się do wszystkich elementów tablicy, natomiast $arrayrozwija się tylko do niepustych elementów.

  • @Flag ekspansja parametr czasami wymaga cudzysłowie całej substytucji: "${(@)foo}".

  • Podstawienie polecenia podlega podziałowi pola, jeśli nie jest cytowane: echo $(echo 'a'; echo '*')drukuje a *(z pojedynczą spacją), podczas gdy echo "$(echo 'a'; echo '*')"drukuje niezmodyfikowany ciąg dwuwierszowy. Użyj, "$(somecommand)"aby uzyskać wynik polecenia w jednym słowie, bez ostatnich znaków nowej linii. Użyj, "${$(somecommand; echo _)%?}"aby uzyskać dokładny wynik polecenia, w tym końcowe znaki nowej linii. Użyj, "${(@f)$(somecommand)}"aby uzyskać tablicę wierszy z danych wyjściowych polecenia.

Gilles
źródło
W rzeczywistości musisz pominąć cudzysłowy, aby zmienna była analizowana jako wyrażenie arytmetyczne. Dlaczego mogę sprawić, by twój przykład działał z cytatami:echo "$(("$expr"))"
Cyker
Oto, co man bashmówi: Wyrażenie jest traktowane tak, jakby znajdowało się w podwójnych cudzysłowach, ale podwójne cudzysłowy w nawiasach nie są traktowane specjalnie.
Cyker,
4
Ponadto, dla każdego, kto jest zainteresowany, formalne nazwy split + glob to dzielenie słów i rozwijanie nazw ścieżek .
Cyker
3
Do Twojej wiadomości na StackOverflow , kazałem komuś wyciągnąć język „opcjonalny, gdy oczekuje się nieprzetworzonego ciągu” w tej odpowiedzi, aby bronić nie cytowania argumentu echo. Być może warto spróbować uczynić język jeszcze bardziej wyraźnym (być może „kiedy parser oczekuje surowego ciągu znaków”)
Charles Duffy
2
@CharlesDuffy Ugh, nie myślałem o tym błędnym odczytaniu. Zmieniłem „gdzie” na „kiedy” i wzmocniłem zdanie, jak zasugerowałeś.
Gilles,