抱歉,我在进行初始测试时一定犯了一些错误,因为将所有内容放入单个脚本后,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 输出是相同的。再次对此表示歉意!
所以我剩下的问题是:
为什么会
Case 1E
导致输出helloworld
哪些字节序列真正包含在 TEST1 和 TEST2 中,哪种是找出这些序列的正确且可移植的方法?
如何让 printf 解释 TEST2 中编码的换行符类型?
以下分配是否可移植(从某种意义上说,它总是会在变量中产生相同的二进制内容?
$ 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 上为代码块添加一些自定义颜色,以便更好地可视化问题(例如,用相同的颜色为相同的输出着色)。
第二:说完这些,有人能通过解释影响这些结果的基本规则来帮助我理解这些输出吗?
例如,有些事情令我困惑:
即使打印的标准输出是不同的对于
TEST1
和TEST2
(例如,Case 1A
导致输出与不同case 2A
),似乎 xxd 作为输入接收的实际字节是完全相同的在所有相应的TEST1
和TEST2
情况下(我的意思是在所有相应的情况下,Case 1x
xxd 输出始终与 相同Case 2x
,即使相同命令的相应 stdout 输出不相等)。这怎么可能呢?TEST1
显然和的内容TEST2
必须有所不同,否则回显/打印它们不可能导致不同的 stdout 输出。那么,我如何才能正确输出这些变量中包含的实际位(十六进制或其他格式,只要它能清晰地表示实际变量内容,就没关系)?这些案例
TEST1
表明,0A
当打印输出还显示换行符时,xxd 会准确收到换行符 ASCII 字符。然而,在打印换行符但没有产生字符的情况下,并没有打印换行符,但产生了字符TEST2
Case 2B
0A
Case 2F
0A
我有点明白,似乎换行符在TEST1
和TEST2
变量中的编码方式不同,并且当回显双引号时似乎会扩展(这是正确的术语吗?)包含在中的换行符类型TEST1
,而回显的 -e 标志似乎可以解释编码的换行符类型TEST2
,但这并不能解释 xxd 输出以及 printf 情况。
为什么会
Case 1E
导致$ printf "%s" $TEST1 helloworld
如何使 printf 应用变量中编码的换行符类型
TEST2
?这里最重要的教训是什么?
注:我没有添加
$ 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 TEST1
或declare -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() 似乎毫无意义,但printf
Bash 中的命令将重复模式直到它完全用完参数,这意味着上述内容实际上等同于:
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 和终端混合处理它们,则根据操作的执行顺序,会产生不同的结果。