Почему замена команд изменяет работу котируемых аргументов?
У меня есть следующий фрагмент:
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 $(...)
для переоценки работы кавычек. Хотя, конечно, используйте это экономно, и совсем нет, если вы не можете доверять выводам подстановки команд.