将命令输出存储到变量
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"