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.


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.


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,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.