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

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

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

Ответ 1

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

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

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

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

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

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

Не упоминается о лечении цитат специально при этом.

Ответ 2

TL; DR

Bash не видит, что вы думаете, что он должен видеть, потому что 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 не переинтерпретирует символы цитирования, которые остаются после подстановки команды, но выполняет word-splitting после выполнение расширений оболочки. Например:

$ 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'

Итак, Bash видит ''\''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

Ответ 3

Следующее:

$(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)

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

Ответ 4

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

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