Почему подстановка команд меняет работу аргументов в кавычках?

У меня есть следующий фрагмент:

printf "%s\n%s\n%s" $(echo "'hello world' world")

Я ожидал бы, что это произведет:

hello world
world

Но на самом деле это производит:

'hello
world'
world

Почему приведенная выше команда отличается от следующей?

printf "%s\n%s\n%s" 'hello world' world

4 ответа

Решение

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

Из tbe Bash Справочное руководство

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

Описание разбиения слов:

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

Оболочка обрабатывает каждый символ $IFS как разделитель и разбивает результаты других расширений на слова на этих символах. Если IFS не установлен, или его значение точно <space><tab><newline>, по умолчанию, то последовательности <space>, <tab>, а также <newline> в начале и в конце результаты предыдущих расширений игнорируются, и любая последовательность символов IFS, отсутствующая в начале или конце, служит для разделения слов. Если IFS имеет значение, отличное от значения по умолчанию, то последовательности пробельных символов пробела и табуляции игнорируются в начале и конце слова, пока символ пробела находится в значении IFS (символ пробела IFS). Любой символ в IFS, который не является пробелом IFS, вместе с любыми соседними символами пробела IFS разделяет поле. Последовательность пробельных символов IFS также рассматривается как разделитель. Если значение IFS равно нулю, разделение слов не происходит.

Никаких упоминаний о трактовке кавычек специально не делается при этом.

TL; DR

Bash не видит того, что, как вы думаете, он должен видеть, потому что Bash разделяет слова после подстановки команд.

Что видит Баш после разделения слов

Ваши два примера кода не эквивалентны. В вашем примере без подстановки подстановка команд не выполняется, поэтому цитирование из шага 2 операции оболочки приведет к тому, что ваш текст будет рассматриваться как два аргумента. Например:

$ set -x
$ printf "%s\n%s\n%s" 'hello world' world
+ printf '%s\n%s\n%s' 'hello world' world
hello world
world

В другом примере Bash не интерпретирует символы кавычек, которые остаются после подстановки команд, но выполняет разбиение по словам после расширений оболочки. Например:

$ set -x
$ printf "%s\n%s\n%s" $(echo "'hello world' world")
++ echo ''\''hello world'\'' world'
+ printf '%s\n%s\n%s' ''\''hello' 'world'\''' world
'hello
world'

Итак, Баш видит ''\''hello' 'world'\''' world а затем разбивает слова в соответствии с $ IFS. Это оставляет буквальные одинарные кавычки в первых двух словах, и последнее слово не используется вашим оператором printf. Более простой способ увидеть это - изменить последнее слово или удалить лишние кавычки:

# The third argument is never used.
$ printf "%s\n%s\n%s" $(echo "'hello world' unused_word")
'hello
world'

# Still breaks into three words, but no quote literals are left behind!
$ printf "%s\n%s\n%s" $(echo 'hello world' unused_word)
hello
world

Много дискуссий почему, но никто не удосужился добавить, как обойти это поведение?

Пока я нашел бег eval $(...) переоценить цитаты работ. Хотя, конечно, используйте это экономно, и совсем нет, если вы не можете доверять выводу подстановки команд.

Следующие:

$(echo "'hello world' world")

выводит три токена: 'hello, world', world, Эти три жетона передаются printf сюда:

printf "%s\n%s\n%s" ('hello) (world') (world)

и не так

 printf "%s\n%s\n%s" ('hello world') (world)

(обратите внимание, что круглые скобки предназначены только для визуального разделения токенов, чтобы проиллюстрировать это).

Другие вопросы по тегам