解释正在发生的事情

解释正在发生的事情

有没有办法让 bash 在反引号替换中不吃掉换行符?

例如:

var=`echo line one && echo line two`
echo $var

line one line two

我想要的是

var=`echo line one && echo line two` # plus some magic
echo $var

line one
line two

答案1

这不是反引号替换的问题,但使用echo; 时必须引用变量才能使控制字符正常工作:

$ var=`echo line one && echo line two`
$ echo "$var"
line one
line two

答案2

简而言之,需要在捕获输出时在输出末尾添加后缀,在使用变量时删除后缀,并始终用引号括住变量:

# add a suffix to the end of the output when capturing it
var=$( cat /no/file/here 2>&1; echo extra)
# when using the variable, remove the suffix, and quote the variable
echo -n "${var%extra}"

将其与以下内容进行比较,其中第二个命令的输出不会以换行符结尾。

var=$( cat /no/file/here 2>&1)
echo -n "${var}"

解释正在发生的事情

字符串中的换行符

首先确定预期的字节序列:

$ { echo a; echo b; } | xxd
0000000: 610a 620a                                a.b.

现在,使用命令替换(第 3.5.4 节)尝试捕获此字节序列:

$ x=$( echo a; echo b; )

然后,进行一个简单的回显来验证字节序列:

$ echo $x | xxd
0000000: 6120 620a                                a b.

因此,看起来第一个换行符被替换为空格,而第二个换行符保持不变。但是为什么呢?

让我们看看这里实际发生了什么:

首先,bash 可以做到Shell 参数扩展(第 3.5.3 节)

'$' 字符用于引入参数扩展、命令替换或算术扩展。要扩展的参数名称或符号可以用括号括起来,括号是可选的,但可以保护要扩展的变量免受紧随其后的字符的影响,因为这些字符可能会被解释为名称的一部分。

然后 bash 就可以了分词(第 3.5.7 节)

shell 会扫描双引号内未发生的参数扩展、命令替换和算术扩展的结果以进行分词。

shell 将 $IFS 的每个字符视为分隔符,并根据这些字符将其他扩展的结果拆分为单词。如果 IFS 未设置,或者其值恰好为 ,...

接下来,bash 会将其视为简单命令(第 3.2.1 节)

简单命令是最常见的命令类型。它只是由空格分隔的单词序列,由 shell 的控制运算符之一终止(请参阅定义)。第一个单词通常指定要执行的命令,其余单词是该命令的参数。

定义空白(第 2 节 - 定义)

空白 空格或制表符。

最后,bash 调用echo(第 4.2 节 - Bash 内置命令)内部命令

... 输出参数,以空格分隔,以换行符结束。...

总而言之,换行符通过单词拆分删除,然后 echo 获取 2 个参数“a”和“b”,然后以空格分隔并以换行符结尾输出它们。

按照@cyrus 的建议做(并使用 -n 抑制 echo 中的换行符),结果会更好:

$ echo -n "$x" | xxd
0000000: 610a 62                                  a.b

字符串末尾有换行符

但它仍然不完美,结尾的换行符不见了。仔细看看命令替换(第 3.5.4 节)

Bash 通过执行命令并用命令的标准输出替换命令替换来执行扩展,并删除所有尾随的换行符。

现在清楚了为什么换行符会消失,可以欺骗 bash 保留它。为此,在末尾添加一个额外的字符串,并在使用变量时将其删除:

$ x=$( echo a; echo b; echo -n extra )
$ echo -n "${x%extra}" | xxd
0000000: 610a 620a                                a.b.

另一个例子

$ cat /no/file/here 2>&1 | xxd
0000000: 6361 743a 202f 6e6f 2f66 696c 652f 6865  cat: /no/file/he
0000010: 7265 3a20 4e6f 2073 7563 6820 6669 6c65  re: No such file
0000020: 206f 7220 6469 7265 6374 6f72 790a        or directory.
$ cat /no/file/here 2>&1 | cksum
3561909523 46
$ 
$ var=$( cat /no/file/here 2>&1; rc=${?}; echo extra; exit ${rc})
$ echo $?
1
$ 
$ echo -n "${var%extra}" | xxd
0000000: 6361 743a 202f 6e6f 2f66 696c 652f 6865  cat: /no/file/he
0000010: 7265 3a20 4e6f 2073 7563 6820 6669 6c65  re: No such file
0000020: 206f 7220 6469 7265 6374 6f72 790a        or directory.
$ echo -n "${var%extra}" | cksum
3561909523 46

答案3

我本想在对 cYrus 的回答的评论中添加一些小补充,但我认为我还不能对答案发表评论。因此,为了完整起见,我将复制 cYrus 的部分回答。

代码第一行发生的情况是,反引号替换确实会吃掉替换命令输出的尾随换行符,如下所示记录. 但它不会吃掉两行之间的换行符。

第二行代码中发生的事情是 echo 使用两个参数执行,即第一行和第二行。引用扩展变量将修复该问题:echo "$var"这将保留第一行和第二行之间的换行符。并且echo默认情况下会添加尾随换行符,以便一切正常。

现在,如果你想要保留某些命令替换输出中的所有尾随换行符,一种解决方法是在输出中添加另一行,你可以在命令替换后将其删除,请参阅这里

相关内容