Основная проблема заключается в том, что нет разницы между открытой и закрытой галочкой. Итак, если оболочка видит что-то вроде этого:
somecommand ` something1 ` something2 ` something3 `
... нет встроенного способа определить, являются ли это двумя отдельными командами с обратной галочкой (something1
и something3
) с буквальной строкой ("something2") между ними; или вложенное выражение обратной кавычки, где сначала запускается something2
, а его вывод передается в something1
в качестве аргумента (вместе с буквальной строкой "something3"). Чтобы избежать двусмысленности, синтаксис оболочки выбирает первую интерпретацию и требует, чтобы, если вам нужна вторая интерпретация, вам нужно было избежать внутреннего уровня обратных кавычек:
somecommand ` something1 ` something2 ` something3 ` # Two separate expansions
somecommand ` something1 \` something2 \` something3 ` # Nested expansions
А это означает добавление еще одного уровня разбора и удаления escape-последовательностей, что означает, что вам нужно избегать любых escape-последовательностей, которые вы не хотели анализировать в тот момент, и все это быстро выходит из-под контроля.
Синтаксис $( )
, с другой стороны, не является двусмысленным, поскольку маркеры открытия и закрытия не совпадают. Сравните две возможности:
somecommand $( something1 ) something2 $( something3 ) # Two separate expansions
somecommand $( something1 $( something2 ) something3 ) # Nested expansions
Здесь нет двусмысленности, поэтому нет необходимости в экранировании или других синтаксических странностях.
Причина, по которой количество побегов растет так быстро с количеством уровней, снова состоит в том, чтобы избежать двусмысленности. И это не что-то конкретное для командных расширений с обратными кавычками; это избегание инфляции появляется каждый раз, когда ваша строка проходит несколько уровней синтаксического анализа, каждый из которых применяется (и удаляет) побеги.
Предположим, что оболочка сталкивается с двумя escape-последовательности и обратной кавычкой (\\`
) при анализе строки. Должен ли он анализировать это как символ с двойным экранированием или символ с одиночным экранированием (обратная косая черта), за которым следует обратный символ без экранирования? Если он проходит через три escape-последовательности и обратную кавычку (\\\`
), это означает, что обратная кавычка с тройным экранированием, двойная экранированная кавычка следует за обратной кавычкой без экранирования или одинарная экранированная кавычка, за которой следует одинарная обратная кавычка ?
Оболочка (как и большинство вещей, связанных с escape-последовательностями) избегает двусмысленности, не рассматривая сложенные escape-последовательности как особую вещь. Когда он сталкивается с escape-символом, он применяется только к элементу, находящемуся сразу после него; если сразу после него находится еще один экран, то он экранирует этот один символ и не влияет на то, что после него. Таким образом, \\`
— это escape-последовательность, за которой следует обратная кавычка без экранирования. Это означает, что вы не можете просто добавить еще один escape-последовательность впереди, вы должны добавить escape-последовательность перед каждым достойным escape-символом в строке (включая escape-последовательности из более низких уровней).
Итак, давайте начнем с простой обратной кавычки и поработаем над ее экранированием на разных уровнях:
- Первый уровень легкий, просто избегайте его:
\'
.
- Для второго уровня мы должны экранировать этот экран (
\\
), а затем отдельно экранировать саму обратную кавычку (\`
), что дает в общей сложности три обратных кавычки: \\\`
.
- Для третьего уровня мы должны по отдельности экранировать каждый из этих трех экранов (то есть 3x
\\
) и еще раз экранировать саму обратную кавычку (\`
), что дает в общей сложности семь обратных кавычек: \\\\\\\`
.
Так продолжается, более чем удваивая количество побегов на каждом уровне. От 7 он переходит к 15, затем к 31, затем к 63, затем... Есть веская причина, по которой люди стараются избегать ситуаций с глубоко вложенными побегами.
Да, и, как я уже упоминал, оболочка — не единственная вещь, которая делает это, и это может усложнить ситуацию, потому что разные уровни могут иметь различный синтаксис экранирования, и некоторые вещи могут не нуждаться в экранировании в некоторых случаях. уровни. Например, предположим, что экранируемым является регулярное выражение \s
. Чтобы добавить к этому уровень, вам понадобится только один дополнительный экран (\\s
), потому что «s» не нужно экранировать сам по себе. Дополнительные уровни побега дадут 4, 8, 16, 32 и т. д. побегов.
TLDR; Эй, чувак, я слышал, ты любишь побеги...
P.S. Вы можете использовать опцию оболочки -v
, чтобы заставить ее печатать команды перед их выполнением. С такими вложенными командами он будет печатать каждую из команд по мере их распаковки, поэтому вы можете наблюдать, как экранированные экранированные стек рушатся, когда слои удаляются:
$ set -v
$ echo "this is `echo "a literal \`echo "backtick: \\\\\\\`" \`" `"
echo "this is `echo "a literal \`echo "backtick: \\\\\\\`" \`" `"
echo "a literal `echo "backtick: \\\`" `"
echo "backtick: \`"
this is a literal backtick: `
(Для еще большего удовольствия попробуйте это после set -vx
-- опция -x
напечатает команды после синтаксического анализа, поэтому после того, как вы увидите вложенные команды, вы увидите, что происходит по мере их развертывания. вернуться к последней команде верхнего уровня.)
person
Gordon Davisson
schedule
02.12.2019
$(
,${
,$((
), возможно, это как-то связано. Я думаю, что реализация подстановки вложенных команд с неэкранированными обратными кавычками будет немного сложной и, возможно, ненадежной. Тем не менее, обратные кавычки по-прежнему поддерживаются для совместимости с posix/обратной совместимостью, надеюсь, мы скоро увидим, что от них отказались навсегда. - person oguz ismail   schedule 02.12.2019