(git)Bash:变量中的换行符与 \n 究竟有何不同?

(git)Bash:变量中的换行符与 \n 究竟有何不同?

抱歉,我在进行初始测试时一定犯了一些错误,因为将所有内容放入单个脚本后,xxd 输出确实总是与 stdouput 匹配。

完整脚本如下:https://pastebin.pl/view/454913ec 我正在更新我的问题并保留下面原始(但错误)的问题。

我获得的脚本输出如下:

$ ./test.sh
# Case 1A: echo -n $TEST1
hello world
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 1B: echo -n -e $TEST1
hello world
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 1C: echo -n "$TEST1"
hello
world
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 1D: echo -n -e "$TEST1"
hello
world
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 1E: printf "%s" $TEST1
helloworld
00000000: 6865 6c6c 6f77 6f72 6c64                 helloworld

# Case 1F: $ printf "%s" "$TEST1"
hello
world
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# --------------------------------
# Case 2A: $ echo -n $TEST2
hello\nworld
00000000: 6865 6c6c 6f5c 6e77 6f72 6c64            hello\nworld

# Case 2B: echo -n -e $TEST2
hello
world
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 2C: echo -n "$TEST2"
hello\nworld
00000000: 6865 6c6c 6f5c 6e77 6f72 6c64            hello\nworld

# Case 2D: echo -n -e "$TEST2"
hello
world
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 2E: printf "%s" $TEST2
hello\nworld
00000000: 6865 6c6c 6f5c 6e77 6f72 6c64            hello\nworld

# Case 2F: printf "%s" "$TEST2"
hello\nworld
00000000: 6865 6c6c 6f5c 6e77 6f72 6c64            hello\nworld

因此,xxd 输出至少对于相同的 stdout 输出是相同的。再次对此表示歉意!

所以我剩下的问题是:

  1. 为什么会Case 1E导致输出helloworld

  2. 哪些字节序列真正包含在 TEST1 和 TEST2 中,哪种是找出这些序列的正确且可移植的方法?

  3. 如何让 printf 解释 TEST2 中编码的换行符类型?

  4. 以下分配是否可移植(从某种意义上说,它总是会在变量中产生相同的二进制内容?

$ TEST1="你好

世界” $ TEST2="hello\nworld"

在另一个问题中,我读到语言环境仅适用于扩展时,所以这应该意味着它应该适用,对吗?


原始(但错误)问题:

我使用 git bash 进行了以下测试:

$ TEST1="hello
> world"
$ TEST2="hello\nworld"


# Case 1A:
$ echo -n $TEST1
hello world
$ echo -n $TEST1 | xxd
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 1B:
$ echo -n -e $TEST1
hello world
$ echo -n -e $TEST1 | xxd
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 1C:
$ echo -n "$TEST1"
hello
world
$ echo -n "$TEST1" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 1D:
$ echo -n -e "$TEST1"
hello
world
$ echo -n -e "$TEST1" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 1E:
$ printf "%s" $TEST1
helloworld
$ printf "%s" $TEST1 | xxd
00000000: 6865 6c6c 6f77 6f72 6c64                 helloworld

# Case 1F:
$ printf "%s" "$TEST1"
hello
world
$ printf "%s" "$TEST1" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world
$

# --------------------------------

# Case 2A:
$ echo -n $TEST2
hello\nworld
$ echo -n $TEST2 | xxd
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 2B:
$ echo -n -e $TEST2
hello
world
$ echo -n -e $TEST2 | xxd
00000000: 6865 6c6c 6f20 776f 726c 64              hello world

# Case 2C:
$ echo -n "$TEST2"
hello\nworld
$ echo -n "$TEST2" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 2D:
$ echo -n -e "$TEST2"
hello
world
$ echo -n -e "$TEST2" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world

# Case 2E:
$ printf "%s" $TEST2
hello\nworld
$ printf "%s" $TEST2 | xxd
00000000: 6865 6c6c 6f77 6f72 6c64                 helloworld

# Case 2F:
$ printf "%s" "$TEST2"
hello\nworld
$ printf "%s" "$TEST2" | xxd
00000000: 6865 6c6c 6f0a 776f 726c 64              hello.world
$

首先:我觉得这很令人沮丧。另外,我希望可以在 stackoverflow 上为代码块添加一些自定义颜色,以便更好地可视化问题(例如,用相同的颜色为相同的输出着色)。

第二:说完这些,有人能通过解释影响这些结果的基本规则来帮助我理解这些输出吗?

例如,有些事情令我困惑:

  1. 即使打印的标准输出是不同的对于TEST1TEST2(例如,Case 1A导致输出与不同case 2A),似乎 xxd 作为输入接收的实际字节是完全相同的在所有相应的TEST1TEST2情况下(我的意思是在所有相应的情况下,Case 1xxxd 输出始终与 相同Case 2x,即使相同命令的相应 stdout 输出不相等)。这怎么可能呢?

  2. TEST1显然和的内容TEST2必须有所不同,否则回显/打印它们不可能导致不同的 stdout 输出。那么,我如何才能正确输出这些变量中包含的实际位(十六进制或其他格式,只要它能清晰地表示实际变量内容,就没关系)?

  3. 这些案例TEST1表明,0A当打印输出还显示换行符时,xxd 会准确收到换行符 ASCII 字符。然而,在打印换行符但没有产生字符的情况下,并没有打印换行符,但产生了字符TEST2Case 2B0ACase 2F0A

我有点明白,似乎换行符在TEST1TEST2变量中的编码方式不同,并且当回显双引号时似乎会扩展(这是正确的术语吗?)包含在中的换行符类型TEST1,而回显的 -e 标志似乎可以解释编码的换行符类型TEST2,但这并不能解释 xxd 输出以及 printf 情况。

  1. 为什么会Case 1E导致

     $ printf "%s" $TEST1
     helloworld
    
  2. 如何使 printf 应用变量中编码的换行符类型TEST2

  3. 这里最重要的教训是什么?

注:我没有添加

$ TEST3="hello\n
world"

使问题简短一些。

我还测试了在定义变量时使用单引号“ ”而不是双引号“ ”,这似乎不会影响结果。

答案1

尽管 TEST1 和 TEST2 打印的 stdout 输出不同(例如,案例 1A 产生的输出与案例 2A 不同),但 xxd 作为输入接收的实际字节似乎在所有相应的 TEST1 和 TEST2 案例中都是相同的(对于所有相应的案例,我的意思是案例 1x 始终具有与案例 2x 相同的 xxd 输出,即使相同命令的相应 stdout 输出不相等)。这怎么可能呢?

它们并不相同。我无法使用 Linux 上的 Bash 重现您的结果也不在 Windows 上使用 Git 的 MSYS Bash。

当回显双引号似乎扩展(这是正确的术语吗?)TEST1 中包含的换行符类型

如果你引用了变量扩展,它的值将保持原样。如果你引用变量扩展,其值将被拆分为多个参数在空白处。这是由 shell 本身完成的,无论您使用哪个命令都会发生。

(例外:作为字符串变量分配的一部分完成的扩展不会被拆分。例如,foo=$TEST1将保留原始值。

然而,作为数组赋值的一部分进行的扩展拆分。例如,foo=($TEST1)将生成一个包含hello和 的双元素数组world。)

稍后,当echo命令收到多个参数时,它总是使用一个空格将它们连接起来。

显然,TEST1 和 TEST2 的内容必须有所不同,否则回显/打印它们不可能导致不同的 stdout 输出。那么,我如何才能正确输出这些变量中包含的实际位(十六进制或其他格式,只要它能清晰地表示实际变量内容,就没关系)?

使用typeset -p TEST1declare -p TEST2。 (我认为 Ksh/Zsh 更喜欢 typeset,Bash 更喜欢 declared,两者的作用相同。)

使用printf %s "$TEST1"适用于字符串,尽管上述两个也处理数组。您还可以使用扩展%q,它将对打印值中的任何特殊字符进行反斜杠转义(使用$''-style 引用,然后可以再次在 shell 脚本中使用)。

> printf %q "$TEST1"
$'hello\nworld'

> printf %q "$TEST2"
hello\\nworld

为什么案例 1E 会导致helloworld

如前所述,不带引号的变量扩展会导致其值在空格处拆分并作为多个参数提供。因此,案例 1E 中的命令等同于:

printf "%s" "hello" "world"

虽然在大多数其他语言中 printf() 似乎毫无意义,但printfBash 中的命令将重复模式直到它完全用完参数,这意味着上述内容实际上等同于:

printf %s "hello"
printf %s "world"

如何使 printf 应用 TEST2 变量中编码的换行符类型?

扩展%b的工作原理类似于%s,但还扩展了参数中的反斜杠转义符。

$ printf %b 'Hello\t,\nworld\t!'
Hello   ,
world   !

这里最重要的教训是什么?

不要编写 shell 脚本。

在 shell 脚本中引用变量,除非您确切知道什么时候不这样做。

答案2

我不确定这是否解释了所有的差异,但我相信差异在于 TEST1 包含回车符(\r)而不是换行符(\n)。

另外,这个回车符作为二进制字符,是字符串的一部分,不需要解释就可以发出。

您可以通过以下代码看到差异:

$ echo $TEST1 | od -w32 -t x1c
0000000  68  65  6c  6c  6f  20  3e  20  77  6f  72  6c  64  0a
          h   e   l   l   o       >       w   o   r   l   d  \n

$ echo $TEST2 | od -w32 -t x1c
0000000  68  65  6c  6c  6f  5c  6e  77  6f  72  6c  64  0a
          h   e   l   l   o   \   n   w   o   r   l   d  \n

还应记住,\r\n由终端解释,而不是由 Bash 解释。这意味着,如果 Bash 和终端混合处理它们,则根据操作的执行顺序,会产生不同的结果。

相关内容