Как именно обратная косая черта работает внутри обратных кавычек?
Из часто задаваемых вопросов по Bash:
Обратная косая черта (\) внутри обратных кавычек обрабатывается неочевидным способом:
$ echo "'echo \\a'" "$(echo \\a)"
a \a
$ echo "'echo \\\\a'" "$(echo \\\\a)"
\a \\a
Но FAQ не нарушает правила разбора, которые приводят к этой разнице. Единственная релевантная цитата из man bash
, которую я нашел, была:
Когда используется форма замены в стиле кавычек в старом стиле, обратный слеш сохраняет свое буквальное значение, за исключением случаев, когда следует $, 'или.
Случаи "$(echo \\a)"
и "$(echo \\\\a)"
достаточно просты: обратная косая черта, escape-символ, превращается в буквальную обратную реакцию. Таким образом, каждый экземпляр \\
становится \
на выходе. Но я изо всех сил пытаюсь понять аналогичную логику для случаев backtick. Что является основным правилом и как из этого вытекает наблюдаемый результат?
Наконец, связанный вопрос... Если вы не заключите в кавычки обратные ссылки, вы получите сообщение об ошибке "нет совпадения":
$ echo 'echo \\\\a'
-bash: no match: \a
Что происходит в этом случае?
обновление
Re: мой главный вопрос, у меня есть теория для набора правил, который объясняет все поведение, но все еще не понимаю, как это следует из каких-либо документированных правил в bash. Вот мои предложенные правила....
Внутри обратной косой черты обратный слеш перед персонажем просто возвращает этот символ. То есть, один обратный слеш не имеет никакого эффекта. И это верно для всех персонажей, кроме самой обратной реакции и обратных галочек. В случае самого обратного слэша \\
становится экранирующим обратным слэшем. Он избежит своего следующего персонажа.
Давайте посмотрим, как это закончится на примере:
a=xx
echo "'echo $a'" # prints the value of $a
echo "'echo \$a'" # single backslash has no effect: equivalent to above
echo "'echo \\$a'" # escaping backslash make $ literal
принтами:
xx
xx
$a
Попробуйте онлайн!
Давайте проанализируем оригинальные примеры с этой точки зрения:
echo "'echo \\a'"
Здесь \\
создает экранирующую обратную косую черту, но когда мы "убегаем" a
, мы просто возвращаемся a
, поэтому он печатает a
.
echo "'echo \\\\a'"
Здесь первая пара \\
создает экранирующую обратную косую черту, которая применяется к \
, создавая буквальную обратную косую черту. То есть первые 3 \\\
становятся одним литералом \
в выходных данных. Оставшийся \a
просто производит a
. Конечный результат - \a
.
Ответы
Ответ 1
Логика довольно проста как таковая. Итак, мы смотрим на исходный код bash (4.4) сам
subst.c: 9273
case ''': /* Backquoted command substitution. */
{
t_index = sindex++;
temp = string_extract(string, &sindex, "'", SX_REQMATCH);
/* The test of sindex against t_index is to allow bare instances of
' to pass through, for backwards compatibility. */
if (temp == &extract_string_error || temp == &extract_string_fatal)
{
if (sindex - 1 == t_index)
{
sindex = t_index;
goto add_character;
}
last_command_exit_value = EXECUTION_FAILURE;
report_error(_("bad substitution: no closing \"'\" in %s"), string + t_index);
free(string);
free(istring);
return ((temp == &extract_string_error) ? &expand_word_error
: &expand_word_fatal);
}
if (expanded_something)
*expanded_something = 1;
if (word->flags & W_NOCOMSUB)
/* sindex + 1 because string[sindex] == ''' */
temp1 = substring(string, t_index, sindex + 1);
else
{
de_backslash(temp);
tword = command_substitute(temp, quoted);
temp1 = tword ? tword->word : (char *)NULL;
if (tword)
dispose_word_desc(tword);
}
FREE(temp);
temp = temp1;
goto dollar_add_string;
}
Как видите, в строке вызывается функция de_backslash(temp);
, которая обновляет строку в c. Код той же функции находится ниже
subst.c: 1607
/* Remove backslashes which are quoting backquotes from STRING. Modifies
STRING, and returns a pointer to it. */
char *
de_backslash(string) char *string;
{
register size_t slen;
register int i, j, prev_i;
DECLARE_MBSTATE;
slen = strlen(string);
i = j = 0;
/* Loop copying string[i] to string[j], i >= j. */
while (i < slen)
{
if (string[i] == '\\' && (string[i + 1] == ''' || string[i + 1] == '\\' ||
string[i + 1] == '$'))
i++;
prev_i = i;
ADVANCE_CHAR(string, slen, i);
if (j < prev_i)
do
string[j++] = string[prev_i++];
while (prev_i < i);
else
j = i;
}
string[j] = '\0';
return (string);
}
Вышеприведенное просто делает простую вещь, если есть символ \
и следующий символ - \
или обратный тик или $
, затем пропустите этот символ \
и скопируйте следующий символ
Так что если преобразовать его в Python для простоты
text = r"\\\\$a"
slen = len(text)
i = 0
j = 0
data = ""
while i < slen:
if (text[i] == '\\' and (text[i + 1] == ''' or text[i + 1] == '\\' or
text[i + 1] == '$')):
i += 1
data += text[i]
i += 1
print(data)
Выход того же - \\$a
. А теперь давайте протестируем то же самое в bash
$ a=xxx
$ echo "$(echo \\$a)"
\xxx
$ echo "'echo \\\\$a'"
\xxx
Ответ 2
Сделал еще несколько исследований, чтобы найти ссылку и правило того, что происходит. В Справочном руководстве по GNU Bash говорится
Когда используется форма замещения в старом стиле, обратная косая черта сохраняет свое буквальное значение, за исключением случаев, когда следуют ‘$,‘ 'или ‘\. Первая обратная кавычка, которой не предшествует обратная косая черта, завершает команду замена. При использовании формы $ (команда) все символы между скобки составляют команду; никто не лечится специально.
Другими словами \,\$ и 'inside of' 'обрабатываются синтаксическим анализатором CLI перед заменой команды. Все остальное передается команде подстановки для обработки.
Давайте рассмотрим каждый пример из вопроса. После # я указываю, как подстановка команд была обработана синтаксическим анализатором CLI перед выполнением '' или $().
Ваш первый пример объяснил.
$ echo "'echo \\a'" # echo \a
a
$ echo "$(echo \\a)" # echo \\a
\a
Ваш второй пример объяснил:
$ echo "'echo \\\\a'" # echo \\a
\a
$ echo "$(echo \\\\a)" # echo \\\\a
\\a
Ваш третий пример:
a=xx
$ echo "'echo $a'" # echo xx
xx
$ echo "'echo \$a'" # echo $a
xx
echo "'echo \\$a'" # echo \$a
$a
Ваш третий пример с использованием $()
$ echo "$(echo $a)" # echo $a
xx
$ echo "$(echo \$a)" # echo \$a
$a
$ echo "$(echo \\$a)" # echo \\$a
\xx