Глубоко вложенные команды

Чтобы экранировать символы в bash, Почему синтаксис сбивает с толку при глубоком вложении команд? Я знаю, что существует альтернативный подход с $() для вложения команд. Просто любопытно, почему это так, когда команды вложения с использованием обратных кавычек!

Например:

echo `echo \`echo \\\`echo inside\\\`\``

Выдает результат: inside

Но

echo `echo \`echo \\`echo inside\\`\``

Не удается с,

bash: command substitution: line 1: unexpected EOF while looking for matching ``'
bash: command substitution: line 2: syntax error: unexpected end of file
bash: command substitution: line 1: unexpected EOF while looking for matching ``'
bash: command substitution: line 2: syntax error: unexpected end of file
echo inside\

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


person Aswath    schedule 02.12.2019    source источник
comment
Вы уже знаете, как заставить его работать. Какой у Вас вопрос?   -  person oguz ismail    schedule 02.12.2019
comment
Да, @oguzismail, я знаю, как это взломать, но не могу понять это изнутри. Мне это кажется очень странным!   -  person Aswath    schedule 02.12.2019
comment
Другие расширения, связанные с оценкой индекса, вводятся специальными последовательностями не менее двух символов ($(, ${, $((), возможно, это как-то связано. Я думаю, что реализация подстановки вложенных команд с неэкранированными обратными кавычками будет немного сложной и, возможно, ненадежной. Тем не менее, обратные кавычки по-прежнему поддерживаются для совместимости с posix/обратной совместимостью, надеюсь, мы скоро увидим, что от них отказались навсегда.   -  person oguz ismail    schedule 02.12.2019
comment
Синтаксис обратной кавычки появился намного раньше Bash. Вы должны спросить автора оригинальной оболочки Bourne; но я почти уверен, что ответ был бы чем-то вроде того, что имело смысл в то время.   -  person tripleee    schedule 02.12.2019


Ответы (2)


Основная проблема заключается в том, что нет разницы между открытой и закрытой галочкой. Итак, если оболочка видит что-то вроде этого:

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
comment
adding another level of parsing-and-removing escapes, which means you need to escape any escapes you didn't want parsed at that point, and the whole thing gets quickly out of hand Не могли бы вы вкратце рассказать об этом? - person Aswath; 02.12.2019
comment
Разве что количество обратных косых черт = количество символов, которые нужно экранировать до этого момента. - person Aswath; 02.12.2019

В показанном вами синтаксисе нет ничего сбивающего с толку. Вам просто нужно разбить каждый из уровней один за другим.

На странице руководства GNU bash говорится

Когда используется форма замены обратной кавычки в старом стиле, используйте обратную косую черту retains its literal meaning, за исключением случаев, когда за ними следует $, ` или \.

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

Таким образом, в этом контексте у вложенной замены есть один \ для выхода из обратной кавычки и еще один для выхода из escape-символа (теперь прочитайте приведенную выше цитату, что \ теряет свое особое значение, за исключением случаев, когда за ним следует другой \). Вот почему второй уровень экранирования требует двух дополнительных обратных слэшей, чтобы избежать исходного символа.

echo `echo \`echo \\\`echo inside\\\`\``
#                 ^^^^           ^^^^    

становится

echo `echo \`echo inside\``
#          ^^           ^^

что в свою очередь становится

echo `echo inside`
#    ^           ^

что в итоге становится

echo inside
person Inian    schedule 02.12.2019