反斜杠在反引号中到底是如何工作的?

发布于 2025-01-14 01:17:23 字数 1880 浏览 5 评论 0原文

来自 Bash 常见问题解答

反引号内的反斜杠 (\) 以不明显的方式处理:

 $ echo "`echo \\a`" "$(echo \\a)"
 一个\一个
 $ echo "`echo \\\\a`" "$(echo \\\\a)"
 \a \a

但 FAQ 并没有分解导致这种差异的解析规则。我发现的来自 man bash 的唯一相关引用是:

当使用旧式反引号形式的替换时,反斜杠保留其字面含义,除非后面跟着 $、` 或 .

"$(echo \\a)""$(echo \\\\a)" 情况很简单:反斜杠,转义字符,正在转义自身变成了字面上的强烈反对。因此,\\ 的每个实例在输出中都变为 \。但我正在努力理解反引号情况的类似逻辑。基本规则是什么以及观察到的输出是如何得出的?

最后,一个相关的问题...如果您不引用反引号,则会收到“不匹配”错误:

$ echo `echo \\\\a`
-bash: no match: \a

这种情况下发生了什么?

更新

回复:我的主要问题,我有一套规则的理论可以解释所有的行为,但仍然不知道它是如何从 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

prints:

xx
xx
$a

在线尝试!

让我们从这个角度分析一下原始示例:

echo "`echo \\a`"

这里 \\ 产生转义反斜杠,但是当我们“转义”a,我们只是返回a,所以它打印a

echo "`echo \\\\a`"

这里第一对 \\ 产生一个转义反斜杠,该反斜杠应用于 \,产生一个文字反斜杠。也就是说,前 3 个 \\\ 在输出中成为单个文字 \。剩余的 \a 仅生成 a。最终结果是\a

From the Bash FAQ:

Backslashes (\) inside backticks are handled in a non-obvious manner:

 $ echo "`echo \\a`" "$(echo \\a)"
 a \a
 $ echo "`echo \\\\a`" "$(echo \\\\a)"
 \a \\a

But the FAQ does not break down the parsing rules that lead to this difference. The only relevant quote from man bash I found was:

When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by $, `, or .

The "$(echo \\a)" and "$(echo \\\\a)" cases are easy enough: Backslash, the escape character, is escaping itself into a literal backlash. Thus every instance of \\ becomes \ in the output. But I'm struggling to understand the analogous logic for the backtick cases. What is the underlying rule and how does the observed output follow from it?

Finally, a related question... If you don't quote the backticks, you get a "no match" error:

$ echo `echo \\\\a`
-bash: no match: \a

What's happening in this case?

update

Re: my main question, I have a theory for a set of rules that explains all the behavior, but still don't see how it follows from any of the documented rules in bash. Here are my proposed rules....

Inside backticks, a backslash in front of a character simply returns that character. Ie, a single backslash has no effect. And this is true for all characters, except backlash itself and backticks. In the case of backslash itself, \\ becomes an escaping backslash. It will escape its next character.

Let's see how this plays out in an example:

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

prints:

xx
xx
$a

Try it online!

Let's analyze the original examples from this perspective:

echo "`echo \\a`"

Here the \\ produces an escaping backslash, but when we "escape" a we just get back a, so it prints a.

echo "`echo \\\\a`"

Here the first pair \\ produces an escaping backslash which is applied to \, producing a literal backslash. That is, the first 3 \\\ become a single literal \ in the output. The remaining \a just produces a. Final result is \a.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

太阳哥哥 2025-01-21 01:17:23

逻辑非常简单。因此,我们查看 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] == '

如果有 \ 字符并且下一个字符是 \ ,上面的代码只是做简单的事情或者反引号或$,然后跳过这个\字符并复制下一个字符

所以如果为了简单起见将其转换为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] == '

相同的输出是\\$a 。现在让我们在 bash 中测试相同的内容

$ a=xxx

$ echo "$(echo \\$a)"
\xxx

$ echo "`echo \\\\$a`"
\xxx
)) 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


相同的输出是\\$a 。现在让我们在 bash 中测试相同的内容


)):
        i += 1
    data += text[i]
    i += 1

print(data)

相同的输出是\\$a。现在让我们在 bash 中测试相同的内容

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

相同的输出是\\$a。现在让我们在 bash 中测试相同的内容

The logic is quite simple as such. So we look at bash source code (4.4) itself

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;
}

As you can see calls a function de_backslash(temp); on the string which updates the string in c. The code the same function is below

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] == '

The above just does simple thing if there is \ character and the next character is \ or backtick or $, then skip this \ character and copy the next character

So if convert it to python for simplicity

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] == '

The output of the same is \\$a. And now lets test the same in bash

$ a=xxx

$ echo "$(echo \\$a)"
\xxx

$ echo "`echo \\\\$a`"
\xxx
)) 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); }

The above just does simple thing if there is \ character and the next character is \ or backtick or $, then skip this \ character and copy the next character

So if convert it to python for simplicity


The output of the same is \\$a. And now lets test the same in bash


)):
        i += 1
    data += text[i]
    i += 1

print(data)

The output of the same is \\$a. And now lets test the same in bash

)) 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); }

The above just does simple thing if there is \ character and the next character is \ or backtick or $, then skip this \ character and copy the next character

So if convert it to python for simplicity

The output of the same is \\$a. And now lets test the same in bash

溺渁∝ 2025-01-21 01:17:23

做了更多的研究来找到正在发生的事情的参考和规则。从 GNU Bash 参考手册中指出

当使用旧式反引号形式替换时,反斜杠
保留其字面含义,除非后跟“$”、“`”或“\”。
前面没有反斜杠的第一个反引号终止命令
替代。当使用 $(command) 形式时,之间的所有字符
括号组成命令;没有人受到特殊对待。

换句话说,`` 中的 \、\$ 和 ` 在命令替换之前由 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

Did some more research to find the reference and rule of what is happening. From the GNU Bash Reference Manual it states

When the old-style backquote form of substitution is used, backslash
retains its literal meaning except when followed by ‘$’, ‘`’, or ‘\’.
The first backquote not preceded by a backslash terminates the command
substitution. When using the $(command) form, all characters between
the parentheses make up the command; none are treated specially.

In other words \, \$, and ` inside of `` are processed by the CLI parser before the command substitution. Everything else is passed to the command substitution for processing.

Let's step through each example from the question. After the # I put how the command substitution was processed by the CLI parser before `` or $() is executed.

Your first example explained.

$ echo "`echo \\a`"   # echo \a
 a 
$ echo "$(echo \\a)"  # echo \\a
 \a

Your second example explained:

$ echo "`echo \\\\a`"   # echo \\a
 \a 
$ echo "$(echo \\\\a)"  # echo \\\\a
 \\a

Your third example:

a=xx
$ echo "`echo $a`"    # echo xx 
xx
$ echo "`echo \$a`"   # echo $a
xx
echo "`echo \\$a`"    # echo \$a
$a

Your third example using $()

$ echo "$(echo $a)"     # echo $a
xx
$ echo "$(echo \$a)"    # echo \$a
$a
$ echo "$(echo \\$a)"   # echo \\$a
\xx
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文