Zsh:为什么单引号中的 \n 会被解释?

6

据我理解,\n应该被解释为换行符。

"a\nb"
$'a\nb'

但不包括

'a\nb'

然而,我看到以下内容:

(以下为我 zsh 会话的记录)

-0-1- ~  > zsh --version
zsh 5.1.1 (x86_64-unknown-cygwin)
-0-1- ~  > echo 'a\nb'
a
b
-0-1- ~  >
3个回答

10

zsh本身不会解释'a\nb'中的\n,但是内置的echo会解释。 'a\nb'"a\nb"是等价的,不包含换行符,而是(literal)字符序列\n。另一方面,$'a\nb'实际上包含一个换行符。

这里有两个问题:

1. 引用:

引用用于告诉shell您想要一个字符本身,而不是它在shell语法中可能具有的任何特殊含义。zsh有四种引用类型,其中一些可能会保留或添加一些字符或字符序列的特殊含义:

  1. \: 在单个字符前加上 \ 可以引用该字符。对于 zsh\n 仅表示“我想要字符 n”。由于 n 没有特殊含义,因此与仅写入 n 相同。但是对于像 * 这样的字符,则不同:未引用的情况下,* 用作通配符进行全局匹配,使用 \* 可以避免这种情况(例如:比较 echo /*echo /\* 的输出)。如果要传递字符 \ 字面上,则必须引用它,例如使用另一个 \\\
  2. '...': 与 \ 类似,'...' 中的任何字符都被视为字面意思,包括其他引用方法。 'foo* bar?'foo\*\ bar\? 本质上是等价的(或者如果想要严谨一些,则为 \f\o\o\*\ \b\a\r\?)。只有 ' 本身不能出现在用 '...' 引用的字符串中,因为它会标记引用的结束。(可以通过设置 RC_QUOTES 来更改此行为。如果设置,则一对单引号被视为单个引号:'foo''bar'foo\'bar
  3. "...": 允许参数和命令替换。这意味着在 $ 后面的单词将作为参数名称(例如,"path: $PATH"),而用 $(...)`...` 包装的字符串将用于命令替换(例如,echo "Date: $(date)")。否则,它的行为类似于 '...',但允许使用 \ 引用 `$\"
  4. $'...': 在 $'...' 中的字符串被视为 print 内置命令的字符串参数。只是在这里,一些字母字符确实具有特殊含义: \n 表示换行符,\b 表示退格,\a 表示响铃字符等等。可以使用 \\\' 分别引用 \'。生成的字符串被认为是完全引用的。在 $'...' 中,没有 参数或命令替换。

2. zsh 内置命令 echo:

除了echo二进制文件或bash内置的echo之外,默认情况下,zsh内置的识别(某些)转义序列,如\n\t。虽然通常需要使用/bin/echo或bash内置来明确启用此行为(通常通过传递-e标志:echo -e "foo\nbar"),但对于zsh内置,则需要明确禁用它。可以通过传递-E标志(echo -E 'foo\nbar')或设置BSD_ECHO选项(setopt bsdecho)来实现。在这种情况下,可以像其他类型的echo一样使用-e标志重新启用该功能。

结论

这意味着'a\nb'"a\nb"(以及a\\nb)都被传递为a\nb(字面上),但是zsh内置的echo然后解释\n,导致输出换行。另一方面,$'a\nb'在传递给echo之前已经包含一个字面换行符。

运行

for quoted_string in a\\nb 'a\nb' "a\nb" $'a\nb'; do
    echo '>' $quoted_string '<'
    /bin/echo -e '>' $quoted_string '<'
    echo -E '>' $quoted_string '<'
    /bin/echo '>' $quoted_string '<'
    echo
done

应该会得到以下输出:

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a\nb <
> a\nb <

> a
b <
> a
b <
> a
b <
> a
b <

如您所见,前三种引用方式没有区别,而第四种总是会打印一个新行。


顺便说一下,perl(至少版本号为5;我不知道Perl 6)的行为与您描述的预期行为相同。在perl中,'...'的行为类似于zsh中的行为。"..."perl中的行为则类似于zsh"..."$'...'的组合:变量将被其值替换,而字符序列如\n\t则被特殊处理。


2

关于"a\nb",您的观点是错误的,它不会\n替换为新行符(至少没有通用的方法)。因此,在不使用文字换行的情况下,没有标准的引用机制可以允许字符串中出现换行符,例如:

x="a
b"

这就是为什么发明了$'a\nb'表示法来实现这一点。截至2016年,它不属于POSIX标准,但POSIX专家正在考虑将其添加到shell规范中。像往常一样,不要抱太大的希望 :-)

请注意,在原问题中,'a\nb'和"a\nb"都是4个字符的字符串;zsh的echo实现识别了双字母符号'\n',并在其位置上输出了一个换行符。 - chepner
@chepner 你确定吗?我的zsh(5.2)使用echo "a\nb"命令会输出a\nb(我已经设置了bsdecho)。 - Jens
是的;bsdecho 选项明确禁用转义字符的(POSIX规定的)解释。 - chepner

0

一个新行是一个字符,而不是一个变量,因此没有解释(或扩展)。如果你想打印一个\,你需要转义两次来表示你不想转义一个变量并且你不想打印一个特殊字符。

所以你必须使用echo a\\\\nbecho 'a\\nb'


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接