最后的新行“$(....)”在shell脚本中被删除。为什么?

时间:2013-06-07 19:08:09

标签: shell unix scripting ksh

有人可以解释为什么这两个命令的输出不同吗?

$ echo "NewLine1\nNewLine2\n"
NewLine1
NewLine2
                         <-- Note 2nd newline here
$ echo "$(echo "NewLine1\nNewLine2\n")"
NewLine1
NewLine2
$                        <-- No second newline

有没有什么好方法可以让新行保留在输出结尾的&#34; $(....)&#34; ?我已经考虑过添加一个虚拟字母并将其删除,但我很想知道为什么这些新线条会消失。

3 个答案:

答案 0 :(得分:3)

因为that's what POSIX specifies并且在Bourne shell中一直如此:

  

2.6.3命令替换

     

命令替换允许替换命令的输出   代替命令名称本身。命令替换将发生   当命令括起来如下:

     

$(命令)

     

或(反引号):

     

`command`

     

shell应通过执行命令扩展命令替换   在子shell环境中(请参阅Shell执行环境)和   替换命令替换(命令文本加上   将“$()”或反引号括起来与标准输出   命令,删除一个或多个&lt; newline&gt;的序列人物   替换结束。嵌入式&lt; newline&gt;结束前的字符   输出不得删除;但是,他们可能被视为   字段分隔符并在字段拆分期间消除,具体取决于   IFS的值和有效的引用。如果输出包含   任何空字节,行为都是未指定的。

保留最终换行符的一种方法是

VAR="$(command; echo x)"   # Append x to keep newline(s).
VAR=${VAR%x}               # Chop x.

显示:

$ x="$(whoami; echo x)" ; printf '<%s>\n' "$x" "${x%x}"
<user
x>
<user
>

但为什么要删除尾随换行符?因为通常你会这样想。我也在使用perl进行编程,我无法计算读取行或变量的次数,然后需要删除换行符:

while (defined ($string = <>)) {
    chop $string;
    frobnitz($string);
} 

答案 1 :(得分:2)

如果未删除换行符,则构造如:

x="$(pwd)/filename"

不会有用,但编写Unix的人更喜欢有用的行为。

很久以前(就像1983年,也许是1984年),我曾经在Unix的特定变种上进行了shell更新,但没有删除尾随的换行符。它打破了整个地方的脚本。很快就解决了。

答案 2 :(得分:1)

命令替换会删除每个尾随换行符。

删除一个是有意义的。例如:

basename foo/bar

输出bar\n。在:

var=$(basename foo/bar)

您希望$var包含bar,而不是bar\n

然而在

var=$(basename $'foo/bar\n')

您希望$var包含bar\n(毕竟,换行符与Unix上的文件名中的任何字符一样有效)。但是所有shell都会移除每个尾随换行符。这种错误是在最初的Bourne shell中,甚至rc已经修复了Bourne的大部分缺陷并没有解决这个问题。 (尽管rc具有``(){cmd}语法,不会删除任何换行符。)

在POSIX shell中,要解决此问题,您可以执行以下操作:

var=$(basename -- "$file"; echo .)
var=${var%??}

虽然您正在失去basename的退出状态。你可以解决的问题:

var=$(basename -- "$file" && echo .) && var=${var%??}

${var%??}是删除最后两个字符。第一个是我们在上面添加的.,第二个是由basename添加的一个换行符,我们不再删除命令替换会执行的操作其他换行符(如果有的话)将成为我们想要获得的文件名的一部分,所以我们确实需要它们。

在没有${var%x}运算符的Bourne shell中,你必须采用漫长而复杂的方式来解决它。