有没有办法让 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 的控制运算符之一终止(请参阅定义)。第一个单词通常指定要执行的命令,其余单词是该命令的参数。
空白 空格或制表符。
最后,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
默认情况下会添加尾随换行符,以便一切正常。
现在,如果你想要保留某些命令替换输出中的所有尾随换行符,一种解决方法是在输出中添加另一行,你可以在命令替换后将其删除,请参阅这里。