Расширение переменной bash ${var:+} в этом документе удаляет двойные кавычки?

Я пытаюсь понять, почему Bash удаляет двойные кавычки (но не одинарные) при расширении переменной с помощью ${parameter:+word} (Использовать альтернативное значение), например, в этом документе:

% var=1
% cat <<EOF
> ${var:+"Hi there"}
> ${var:+'Bye'}
> EOF
Hi there
'Bye'

Согласно руководству, «слово» после :+ обрабатывается расширением тильды, расширением параметра, подстановкой команд и арифметическим расширением. Ни один из них не должен ничего делать.

Что мне не хватает? Как я могу получить двойные кавычки в расширении?


person Andreas Luik    schedule 06.12.2016    source источник
comment
Похоже на ошибку или утечку реализации. Невозможно использовать двойную кавычку в качестве альтернативного значения без присвоения ее переменной: dq='"' ; ... ${var:+$dq}   -  person choroba    schedule 06.12.2016
comment
@choroba: ${var:+$(echo '"')} Но в принципе согласен. Есть что-то довольно странное в анализе word в раскрытии параметров в кавычках (расширения параметров в здесь документах раскрываются так, как если бы они были в кавычках, согласно руководству, и, похоже, это так.)   -  person rici    schedule 07.12.2016
comment
Почти эквивалент документа здесь можно рассматривать как эхо ..., и в этом случае удаляются, просто набрав echo "${var:+"Hi there"}" в командной строке. Они возвращаются при использовании \, но документ HERE не является последовательным и выдает \ на выходе вместо . Что-то явно не так.   -  person Gunstick    schedule 08.12.2016


Ответы (3)


тл;др

$ var=1; cat <<EOF
"${var:+Hi there}"
${var:+$(printf %s '"Hi there"')}
EOF

"Hi there"
"Hi there"

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


полезный ответ Йенса и Патрик Полезный ответ Обары проливает свет и дополнительно демонстрирует проблему.

Обратите внимание, что проблемное поведение в равной степени относится к:

  • (как отмечено в других ответах) обычные строки в двойных кавычках (например, echo "${var:+"Hi there"}"; для 1-го обходного пути вам придется \ заключать в кавычки окружающие " экземпляры, например, echo "\"${var:+Hi there}\""; что любопытно, поскольку Gunstick указывает в комментарии к вопросу, используя \" в альтернативном значении для получения " в вывод действительно работает со строками в двойных кавычках — например, echo "${var:+\"Hi th\"ere\"}" — в отличие от здесь-документов без кавычек.)

  • связанные расширения ${var+...}, ${var-...} / ${var:-...} и ${var=...} / ${var:=...}

  • Кроме того, есть связанная странность в отношении \-обработки внутри альтернативных значений в двойных кавычках внутри строки в двойных кавычках / здесь-документа без кавычек: bash и ksh неожиданно удалить встроенные \ экземпляров; например,
    echo "${nosuch:-"a\b"}" неожиданно дает ab, хотя echo "a\b" отдельно дает a\b - см. этот вопрос.

У меня нет объяснения такому поведению[1] , но я могу предложить практичные решения, которые работают со всеми основными POSIX-совместимыми оболочками (dash, bash, ksh, zsh):

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

Обратите внимание, что в расширениях параметров, включающих замену или удаление префикса/суффикса, кавычки do имеют синтаксическое значение; например: echo "${BASH#*"bin"}" или echo "${BASH#*'bin'}" - любопытно, но dash не поддерживает одинарные кавычки.

  • Если вы хотите заключить все альтернативное значение в кавычки, и оно не содержит встроенных кавычек, и вы хотите, чтобы оно расширялось,
    процитируйте полное расширение, которое позволяет обойти проблему удаления " из альтернативного значения:

    # Double quotes
    $ var=1; cat <<EOF
    "${var:+The closest * is far from   $HOME}"
    EOF
    "The closest * is far from   /Users/jdoe"
    
    # Single quotes - but note that the alternative value is STILL EXPANDED,
    # because of the overall context of the unquoted here-doc.
    var=1; cat <<EOF
    '${var:+The closest * is far from   $HOME}'
    EOF
    'The closest * is far from   /Users/jdoe'
    
  • Для встроенных кавычек или для предотвращения расширения альтернативного значения
    используйте подстановку встроенной команды ( без кавычек, хотя он будет вести себя так, как если бы он был заключен в кавычки):

    # Expanded value with embedded quotes.
    var=1; cat <<EOF
    ${var:+$(printf %s "We got 3\" of snow at   $HOME")}
    EOF
    We got 3" of snow at   /Users/jdoe
    
    # Literal value with embedded quotes.
    var=1; cat <<EOF
    ${var:+$(printf %s 'We got 3" of snow at   $HOME')}
    EOF
    We got 3" of snow at   $HOME
    

При необходимости эти два подхода можно комбинировать.


[1] Альтернативное значение:

  • ведет себя как неявно строка в двойных кавычках,
  • ', как и обычные строки в двойных кавычках, обрабатываются как литералы.

Учитывая вышеизложенное,

  • Было бы разумно рассматривать встроенные экземпляры " также как литералы и просто передавать их, как и экземпляры '.
    Вместо этого, к сожалению, они удаляются, и если вы попытаетесь экранировать их как \", \ тоже сохранится (внутри не цитируемых здесь-документов, но, что любопытно, не внутри строк в двойных кавычках), за исключением ksh — похвального исключения — , где \ экземпляра удалены. Любопытно, что в zsh попытка использовать \" полностью прерывает расширение (как и несбалансированные неэкранированные во всех оболочках).

    • More specifically, the double quotes have no syntactic function in the alternative value, but they are parsed as if they did: quote removal is applied, and you can't use (unbalanced) " instances in the interior without \"-escaping them (which, as stated, is useless, because the \ instances are retained).

Учитывая неявную семантику строки в двойных кавычках, литеральные экземпляры $ должны быть либо экранированы \$, либо для встраивания строки в одинарных кавычках ($(printf %s '...')) должна использоваться подстановка команд.

person mklement0    schedule 13.12.2016
comment
Я думаю, что встроенные " обрабатываются как литералы, а затем удаляются, когда bash выполняет оценку значения. Но мне любопытно, почему удаление префикса/суффикса не работает последовательно. Я думаю, мне нужно заглянуть в исходный код bash, чтобы понять, что происходит на самом деле... - person Patryk Obara; 13.12.2016
comment
@PatrykObara: Именно в этом несоответствие: двойные кавычки не имеют синтаксической функции в альтернативном значении, но они анализируются так, как если бы они выполнялись: применяется удаление кавычек, как вы описать, и вы не можете использовать " экземпляры внутри без \"-экранирования их (что бесполезно, потому что \ сохраняется). - person mklement0; 13.12.2016
comment
@PatrykObara: разница может быть неожиданной, но в контексте удаления префикса/суффикса и замены строки цитирование действительно выполняет полезную функцию: отличать метасимволы шаблона (например, *) от литералов (например, "*" ), так что я бы не назвал это несоответствием. - person mklement0; 13.12.2016

Такое поведение выглядит преднамеренным — оно одинаково для всех опробованных мной оболочек Bourne (например, ksh93 и zsh ведут себя одинаково).

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

$ echo "${var:+"hi there"}"
hi there
$ echo "${var:+'Bye'}"
'Bye'

В спецификации POSIX есть только очень слабый намек на то, что для слов в двойных кавычках в расширениях параметров происходит что-то особенное. Это из информативного раздела "Примеры" расширения параметров. :

Двойные кавычки шаблонов различаются в зависимости от того, где они помещены.

"${x#*}"
‹Звездочка› является шаблонным символом.
${x#"*"}
Литерал ‹Звездочка› заключен в кавычки и не является специальным.

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

Обновить

Я попробовал FreeBSD /bin/sh, производную от Almquist Shell. Эта оболочка выводит одинарные и двойные кавычки. Таким образом, поведение больше не одинаково во всех всех оболочках, а только в большинстве оболочек, которые я пробовал.

Что касается двойных кавычек в расширении слова после :+, я считаю

$ var=1
$ q='"'
$ cat <<EOF
${var:+${q}hi there$q}
EOF
"hi there"
person Jens    schedule 12.12.2016

$ cat <<EOF
${var:+bare alt value is string already}
${var:+'and these are quotes within string'}
${var:+"these are double quotes within string"}
${var:+"which are removed during substitution"}
"${var:+but you can simply not substitute them away ;)}"
EOF
bare alt value is string already
'and these are quotes within string'
these are double quotes within string
which are removed during substitution
"but you can simply not substitute them away ;)"

Обратите внимание, что здесь-документ не нужен, чтобы воспроизвести это:

$ echo "${var:+'foo'}"
'foo'
person Patryk Obara    schedule 13.12.2016