为什么使用 `printf` 加入数组会在终端和脚本中产生不同的结果?

Why does joining an array with `printf` give different results in the terminal vs a script?

我正在尝试编写一个脚本,其中涉及打印一个带有分隔符的数组。不幸的是,当 运行 作为脚本时,在命令行中输入的 printf 命令不起作用。 bash 和 zsh 处理事情的方式似乎也不同。

这给出了预期的输出,如果我像这样直接粘贴到终端就可以了:

➜  ~ array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"
a
b
c
d

然而,当运行作为脚本时,输出并不是我想要的:

➜  ~ echo 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"' > test1.sh
➜  ~ bash test1.sh 
abcdn

然后我记得 zsh 是我的默认值 shell,所以我尝试 运行明确地用 zsh 连接它。结果更接近我想要的(每个数组元素之间有一些东西),但仍然不是我粘贴到终端时得到的结果。为什么在这种情况下 \n 中的 \ 会被忽略?

➜  ~ zsh test1.sh
anbncndn

此外,table 基本命令(如 echoprintf 在 shell 和脚本与终端之间的关键区别中是否存在方便的 table 某处?

编辑:

我注意到我的示例存在缺陷,因为当我创建脚本 test.sh 文件时,echo 在 n.

之前遗漏了反斜杠
➜  ~ echo '
array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"
'

array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp %sn "${array[@]}"
echo "$tmp"

但让我失望的是 zsh -c 表现出相同的行为。它是否也去除了反斜杠?

➜  ~ zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"'
anbncndn

编辑 2:转义字符在每种情况下都有效。 前面的三个反斜杠变成一个很好的换行符。

zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\\n' "${array[@]}"
echo "$tmp"'
a
b
c
d

EDIT3:因此脚本中的转义与“-c”不同

脚本:

#!/bin/zsh
array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"

结果:

➜  ~ zsh test.sh     
a
b
c
d

zsh 处理数组的方式与 bash 不同。在 zsh(您的交互式 shell)中,array+=b 确实将字符串 b 作为新的数组元素附加。

然而,在bash中,相同的命令仅将字符串b追加到数组的第一个元素;要追加一个新元素,您需要使用 array+=(b).

zsh -c 'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '%s\n' "${array[@]}"
echo "$tmp"'
anbncndn

在此,-c的参数分为三部分:

'array=()
array+=a
array+=b
array+=c
array+=d
printf -v tmp '
%s\n

' "${array[@]}"
echo "$tmp"'

它们都连接在一起。单引号内的部分不受替换、扩展、反斜杠特殊处理等的影响。中间部分 ,在传递给 [=17 之前变为 %sn =] 执行。当您添加额外的反斜杠时,%s\\n 变为 %s\n,并且双反斜杠通过 zsh 执行命令变为单个反斜杠。

在您的脚本中,因为您没有尝试引用整个内容以使其成为一个参数,所以 printf -v tmp '%s\n' "${array[@]}" 会按照您想要的方式进行拆分和处理。但是,因为您在那里将反斜杠加倍,所以 $tmp 设置为 a\nb\nc\nd\n。然后当你 echo 它时,默认的 zsh 行为是在正在打印的字符串中扩展反斜杠转义序列,所以它看起来是正确的。这可以通过 BSD_ECHO shell 选项或使用 echo -E 来禁用。如果您想要实际的换行符,请在单引号内使用单个反斜杠,或者在双引号或不加引号的情况下使用双反斜杠。

示范:

$ printf -v tmp '%s\n' a b c d
$ echo "$tmp"
a
b
c
d
$ echo -E "$tmp"
a\nb\nc\nd\n
$ setopt BSD_ECHO
$ echo "$tmp"
a\nb\nc\nd\n
$ printf -v tmp '%s\n' a b c d
$ echo "$tmp"
a
b
c
d