Когда необходимо двойное цитирование?


120

Старый совет заключался в том, чтобы заключать в двойные кавычки любое выражение, включающее $VARIABLE, по крайней мере, если кто-то хотел, чтобы оболочка интерпретировала его как один отдельный элемент, в противном случае любые пробелы в содержимом $VARIABLEбудут отбрасывать оболочку.

Однако я понимаю, что в более поздних версиях оболочек двойные кавычки больше не всегда нужны (по крайней мере, для целей, описанных выше). Например, в 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

С zshдругой стороны, те же три команды выполняются успешно. Поэтому, основываясь на этом эксперименте, кажется, что в bash, можно опустить двойные кавычки внутри [[ ... ]], но не внутри и [ ... ]не в аргументах командной строки, тогда как zshво всех этих случаях двойные кавычки могут быть опущены.

Но вывод общих правил из примеров, подобных приведенным выше, является случайным предложением. Было бы неплохо увидеть сводку о том, когда двойные кавычки необходимы. Я в первую очередь интересует zsh, bashи /bin/sh.


10
Ваше наблюдаемое поведение в zsh зависит от настроек и зависит от SH_WORD_SPLITопции.
Ульрих Дангел


3
Кроме того, имена переменных всех заглавных букв используются переменными со значением для операционной системы и оболочки; спецификация POSIX явно рекомендует использовать строчные имена для переменных, определенных приложением. (Несмотря на то, что в приведенной спецификации особое внимание уделяется переменным среды, переменные среды и переменные оболочки совместно используют пространство имен: попытка создать переменную оболочки с именем, уже используемым переменной среды, перезаписывает последнее). См. Pubs.opengroup.org/onlinepubs/009695399/basedefs/… , четвертый абзац.
Чарльз Даффи

Ответы:


128

Сначала отделите зш от остальных. Дело не в старых или современных оболочках: zsh ведет себя по-разному. Разработчики zsh решили сделать его несовместимым с традиционными оболочками (Bourne, ksh, bash), но проще в использовании.

Во-вторых, гораздо проще использовать двойные кавычки, чем помнить, когда они нужны. Они нужны большую часть времени, поэтому вам нужно учиться, когда они не нужны, а не когда они нужны.

В двух словах, двойные кавычки необходимы везде, где ожидается список слов или шаблон . Они являются необязательными в тех случаях, когда синтаксический анализатор ожидает необработанную строку.

Что происходит без кавычек

Обратите внимание, что без двойных кавычек происходят две вещи.

  1. Во-первых, результат раскрытия (значение переменной для подобной подстановки параметра ${foo}или вывод команды для подобной подстановки команды $(foo)) разбивается на слова, где бы они ни были.
    Точнее, результат раскрытия делится на каждый символ, который появляется в значении IFSпеременной (символ-разделитель). Если последовательность символов-разделителей содержит пробелы (пробел, табуляция или перевод строки), этот пробел считается одним символом; ведущие, конечные или повторяющиеся непробельные разделители приводят к пустым полям. Например, с IFS=" :", :one::two : three: :four создает пустые поля перед one, между oneи two, и (один) между threeи four.
  2. Каждое поле, полученное в результате разбиения, интерпретируется как глобус (шаблон с подстановочными знаками), если оно содержит один из символов \[*?. Если этот шаблон соответствует одному или нескольким именам файлов, шаблон заменяется списком соответствующих имен файлов.

Расширение переменной без кавычек $fooв разговорной речи называется «оператор split + glob», в отличие от того, "$foo"которое просто принимает значение переменной foo. То же самое касается подстановки команд: "$(foo)"подстановка $(foo)команд, подстановка команд, за которой следует split + glob.

Где вы можете опустить двойные кавычки

Вот все случаи, которые я могу вспомнить в оболочке в стиле Борна, где вы можете написать переменную или подстановку команд без двойных кавычек, и значение интерпретируется буквально.

  • На правой стороне задания.

    var=$stuff
    a_single_star=*

    Обратите внимание, что вам нужны двойные кавычки после export, потому что это обычное встроенное, а не ключевое слово. Это верно только для некоторых оболочек, таких как dash, zsh (в эмуляции sh), yash или шикарный; bash и ksh оба обрабатывают exportспециально.

    export VAR="$stuff"
  • В caseзаявлении.

    case $var in 

    Обратите внимание, что вам нужны двойные кавычки в регистре. Разделение слов не происходит в шаблоне падежа, но переменная без кавычек интерпретируется как шаблон, тогда как переменная в кавычках интерпретируется как буквенная строка.

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
  • В двойных скобках. Двойные скобки имеют специальный синтаксис оболочки.

    [[ -e $filename ]]

    За исключением того, что вам нужны двойные кавычки, где ожидается шаблон или регулярное выражение: справа от =или ==или !=или =~.

    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

    Вам нужны двойные кавычки, как обычно, в одинарных скобках, [ … ]потому что это обычный синтаксис оболочки (это команда, которая вызывается [). Смотрите одинарные или двойные скобки

  • В перенаправлении в неинтерактивных оболочках POSIX (нет bash, ни ksh88).

    echo "hello world" >$filename

    Некоторые оболочки в интерактивном режиме обрабатывают значение переменной как шаблон с подстановочными знаками. POSIX запрещает такое поведение в неинтерактивных оболочках, но некоторые оболочки, включая bash (за исключением режима POSIX) и ksh88 (в том числе, когда он обнаруживается как (предположительно) POSIX shнекоторых коммерческих Unices, таких как Solaris), все еще делают это там ( bashтакже пытается разделить и перенаправление не удается , если этот раскол не + подстановка результатов в точности одно слово), поэтому лучше процитирую целей перенаправлений в shсценарии в случае , если вы хотите , чтобы преобразовать его в bashсценарий когда- нибудь, или запустить его в системе , где shнаходится не соответствует этому пункту, или он может быть получен из интерактивных оболочек.

  • Внутри арифметического выражения. Фактически, вам нужно оставить кавычки, чтобы переменная была проанализирована как арифметическое выражение.

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

    Тем не менее, вам нужны кавычки вокруг арифметического расширения, так как в большинстве оболочек они разбиваются на слова, как того требует POSIX (!?).

  • В ассоциативном массиве индекс.

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

Переменная без кавычек и подстановка команд могут быть полезны в некоторых редких случаях:

  • Когда значение переменной или вывод команды состоит из списка шаблонов глобуса, и вы хотите расширить эти шаблоны до списка подходящих файлов.
  • Если вы знаете, что значение не содержит подстановочных знаков, это $IFSне было изменено, и вы хотите разделить его на пробельные символы.
  • Если вы хотите разделить значение по определенному символу: отключите глобализацию с помощью set -f, установите IFSсимвол разделителя (или оставьте его в покое, чтобы разделить пробел), затем выполните расширение.

Zsh

В zsh вы можете опускать двойные кавычки большую часть времени, за некоторыми исключениями.

  • $varникогда не расширяется до нескольких слов, однако расширяется до пустого списка (в отличие от списка, содержащего одно пустое слово), если значением varявляется пустая строка. Контраст:

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

    Аналогично, "${array[@]}"расширяется на все элементы массива, а $arrayраспространяется только на непустые элементы.

  • @Флаг расширения параметра иногда требует двойных кавычек всей подстановки: "${(@)foo}".

  • Подстановка команды подвергается разделению поля, если не заключена в кавычки: echo $(echo 'a'; echo '*')печатает a *(с одним пробелом), тогда как echo "$(echo 'a'; echo '*')"печатает неизмененную двухстрочную строку. Используйте, "$(somecommand)"чтобы получить вывод команды в одном слове, без финальных строк. Используйте "${$(somecommand; echo _)%?}"для получения точного вывода команды, включая заключительные символы новой строки. Используется "${(@f)$(somecommand)}"для получения массива строк из вывода команды.


Фактически, вам нужно оставить кавычки, чтобы переменная была проанализирована как арифметическое выражение. Почему я могу заставить ваш пример работать с цитатами:echo "$(("$expr"))"
Cyker

Это то, что man bashговорит: выражение обрабатывается так, как если бы оно было в двойных кавычках, но двойные кавычки в скобках специально не обрабатываются.
Cyker

4
Кроме того, для всех, кто интересуется, формальными именами split + glob являются разделение слов и расширение пути .
Cyker

3
К вашему сведению - в StackOverflow у меня есть кто-то, кто вытащит язык «необязательный, когда ожидается необработанная строка» в этом ответе, чтобы защитить, не цитируя аргумент echo. Возможно, стоит попытаться сделать язык еще более явным (возможно, когда синтаксический анализатор ожидает необработанную строку)
Чарльз Даффи,

2
@CharlesDuffy Тьфу, я не думал об этом неправильном прочтении. Я изменил «где» на «когда» и усилил предложение, как вы предложили.
Жиль
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.