Contents

将命令输出存储到变量

1. 概述

当我们使用命令替换 技术编写 shell 脚本时,我们可以将命令的输出保存在一个变量中。

有时,命令的输出可以有多行。如果我们不以正确的方式使用 shell 变量,我们可能会丢失所有的换行符。

在本教程中,我们将了解为什么变量中的换行符可能会在输出中消失。此外,我们将讨论在命令中使用 shell 变量的正确方法。

当我们在本教程中讨论 shell 时,我们关注的是 Bash。

2. 问题介绍

一个例子可以很快地解释这个问题。假设我们有一个文本文件input.txt

$ cat input.txt 
1, GNU Linux
2, Apple MacOS
3, MS Windows

如上面的输出所示,input.txt文件包含三行。

现在,让我们 使用命令替换将上面cat命令的输出分配给 shell 变量:

VAR=$(cat input.txt)

让我们看一下VAR变量,看看它是否有我们的期望值:

$ echo $VAR
1, GNU Linux 2, Apple MacOS 3, MS Windows

输出显示连接在一起的三行。也就是说,我们似乎已经丢失了所有换行符。

接下来,我们将讨论并寻找几个问题的答案:

  • 我们真的会丢失*$VAR*变量中的换行符吗?
  • 为什么echo 命令的输出中没有换行符?
  • 保留换行符的正确方法是什么?

3. 我们真的丢失了*$VAR*变量中的换行符吗?

当我们查看 echo命令的输出时,我们可能会推断我们已经丢失了所有换行符。

但这是否意味着*$VAR*变量不包含换行符?让我们看看另一个测试:

$ cat <<<$VAR
1, GNU Linux
2, Apple MacOS
3, MS Windows

这一次,我们使用了一个here-string 来使用*$VAR变量的值来提供cat命令 的stdin*。我们可以看到换行符在那里。

也就是说,通过命令替换分配变量后,我们不会丢失换行符VAR=$(command)

自然地,问题就来了:为什么echo命令将三行打印为一行?

接下来,就让我们一起来看看答案吧。

4. 为什么echo命令的输出中没有换行符?

到目前为止,我们已经了解到我们的 $VAR变量包含换行符。

要回答为什么在echo $VAR的输出中换行符会消失,让我们分解并了解执行echo $VAR 时会发生什么。

首先,shell 将读取*$VAR变量的值并将其拆分为参数,使用IFS 环境变量来提供echo*命令。

IFS变量的默认值是空格,其中包含空格、制表符和换行符。

因此,我们的 echo $VAR变为:

echo "1," "GNU" "Linux" "2," "Apple" "MacOS" "3," "MS" "Windows"

显然,上面命令的输出不会包含换行符。

为了验证这一点,我们可以创建一个简单的 shell 脚本:

$ cat ifs_test.sh
#!/bin/bash
VAR=$(cat input.txt)
OLD_IFS="$IFS"
IFS=""
echo $VAR
IFS="$OLD_IFS"

脚本非常简单。在我们调用echo $VAR之前,我们将IFS变量设置为一个空字符串来告诉 shell 不要执行拆分

执行完echo命令后,我们恢复IFS变量。

现在,让我们看看如果我们执行脚本会打印什么:

$ ./ifs_test.sh 
1, GNU Linux
2, Apple Mac OS
3, MS Windows

是的,这一次,我们看到了预期的换行符。

但是更改和恢复IFS 并不是那么方便。我们不能在 shell 脚本中用 shell 变量包装所有命令调用。

接下来,让我们看看在输出中保留换行符的正确方法。

5. 保留换行符的正确方法

在介绍问题的解决方案之前,我们先比较两个命令:

$ echo $VAR
1, GNU Linux 2, Apple MacOS 3, MS Windows
$ echo "$VAR"
1, GNU Linux
2, Apple MacOS
3, MS Windows

使用变量的正确方法很简单:引用变量

这是因为如果我们引用一个变量,shell 会将其视为命令的一个参数,无论该变量是否包含换行符。

值得一提的是,当我们引用一个 shell 变量时,我们应该使用双引号,因为该变量不会在单引号内展开

$ echo '$VAR'
$VAR

事实上,如果我们在命令中使用变量,我们应该总是引用它们。有时,使用不带引号的 shell 变量可能很危险。

让我们看另一个例子。假设我们使用以下方法分配了*$FILENAME*变量:

FILENAME=$(some_command)

然后,让我们执行:

$ rm $FILENAME

上面的命令看起来不错。它将删除存储在*$FILENAME变量中的文件。例如,如果我们有$FILENAME=”not_important.txt”,文件not_important.txt*将被删除。

但是,大多数 Linux 文件系统都允许文件名中有空格。有一天,“ some_command ”可能会返回一个文件名“ not important.txt ”。然后,我们的 rm命令变成:

rm not important.txt

这里的问题很明显。如果我们在当前目录中有名为“ important.txt ”或“ not ”的文件,我们的命令将意外删除它们。

解决问题的方法是简单地引用 shell 变量:

rm "$FILENAME"