5

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

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

有什么好方法可以将新行保留在 "$( .... )" 中的输出末尾吗?我考虑过只添加一个虚拟字母并将其删除,但我很想了解为什么这些新行会消失。

4

3 回答 3

3

因为这就是 POSIX 指定的并且在 Bourne shell 中一直是这样的:

2.6.3 命令替换

命令替换允许替换命令的输出来代替命令名称本身。当命令包含以下内容时,应进行命令替换:

$(命令)

或(反引号版本):

`命令`

shell 应通过在子 shell 环境中执行命令来扩展命令替换(请参阅 Shell 执行环境)并用命令的标准输出替换命令替换(命令文本加上封闭的“$()”或反引号),删除替换结束时的一个或多个 <newline> 字符序列。输出结束前嵌入的 <newline> 字符不应被删除;但是,它们可能被视为字段分隔符并在字段拆分期间被消除,具体取决于 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);
} 
于 2013-06-07T21:38:46.450 回答
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 中,您必须采取漫长而复杂的方式来解决它。

于 2013-06-07T21:32:15.277 回答
1

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

x="$(pwd)/filename"

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

很久以前(比如 1983 年,可能是 1984 年)曾经,我遇到了一个特定 Unix 变体的 shell 更新,它没有删除尾随的换行符。它到处破坏脚本。它很快就修好了。

于 2013-06-07T23:09:31.840 回答