Contents

命令行中用文本文件中的值替换 shell 变量

1. 概述

我们经常使用模板来创建遵循相同模式或规则的文档。例如,我们填充网页模板的动态部分以生成各种网页。

通常,模板是带有一些变量的文本文件。当我们想要从模板创建具体文档时,我们将模板中的变量替换为它们的实际值。

在本教程中,我们将学习如何在 Linux 命令行中用文本文件中的值替换 shell 变量。

2. 问题介绍

像往常一样,让我们通过一个例子来解释这个问题。

首先,让我们看一下我们的模板文件:

$ cat template.txt 
Hey $USER,
Nice to see you on this server!
Your home directory is: $HOME

我们的template.txt文件是纯文本文件。这是给服务器上登录用户的消息。

正如我们在上面的文件内容中看到的,模板包含 shell 变量以使消息动态化。因此,要生成具体的消息,我们需要将模板文件中的 shell 变量替换为它们的实际值

接下来,让我们看看如何从模板生成消息。

我们将扩展当前的模板文件以涵盖本教程后半部分的新要求。

3. eval不是正确的方法

我们知道**eval 命令 可以通过连接参数来构造一个命令**。

因此,为了解决这个问题,一个想法是从我们的模板文件中读取每一行并构建一个命令eval echo “$line”。如果“ $line ”包含 shell 变量,eval将评估它们作为它们的值。

3.1. 使用eval构建命令

现在,让我们实现这个想法并测试它是否可以从模板文件创建预期的消息:

$ rm -f message.txt; while read line
do  
    eval echo "$line" >> message.txt
done < "template.txt" 
$ cat message.txt
Hey kent,
Nice to see you on this server!
Your home directory is: /home/kent

我们构建了一个命令,将eval命令置于while循环中 ,以将每一行附加到名为message.txt的输出文件中 。

执行后,如 cat的输出所示,message.txt文件包含预期的内容。此外,所有 shell 变量都已替换为它们的值。

3.2. 一些问题

看来我们找到了解决问题的方法。

但是,eval方法可能存在一些问题。让我们在template.txt文件中再添加两行:

$ cat template.txt 
...
# If you like using Java, the Java home is: $JAVA_HOME
Also, you can use command substitution: $(YOUR_CMD)

现在,让我们重新运行我们的命令,看看它会创建什么消息:

$ rm -f message.txt; while read line
do
    eval echo "$line" >> message.txt
done < "template.txt" 
zsh: command not found: YOUR_CMD

正如我们所看到的,我们在执行命令时收到了一条错误消息。这是因为我们在模板中添加了*$(YOUR_CMD)* 。它不是一个变量,所以我们希望在创建的message.txt文件中看到它没有变化。但是,eval将评估连接的段。也就是说,它会真正执行*$(YOUR_CMD)作为命令替换。但是,当然,没有可用的YOUR_CMD*命令。

我们在此示例中添加了一个无效命令并看到了错误消息。我们很幸运,因为该错误不会对系统造成损害。然而,想象一下,如果我们添加了一些有效的命令,例如“ rm *”——我们可能会得到意想不到或不希望的结果。因此,使用eval来解决这个问题可能是危险的

接下来虽然命令执行有错误,但是我们看一下它生成的message.txt

$ cat message.txt 
Hey kent,
Nice to see you on this server!
Your home directory is: /home/kent
Also, you can use command substitution:

如上面的输出所示,由于我们一直在谈论的错误,最后一行已被截断。

除此之外,我们可以看到以“ # ”开头的行变成了一个空行。这是因为,在评估期间,** eval将以“ # ”开头的行视为注释**并将它们评估为空。

因此,  eval不是解决这个问题的正确工具。

4. 使用envsubst命令

我们已经了解了为什么eval不是解决此问题的正确工具。那么现在,让我们介绍一下*envsubst *命令。

envsubst 实用程序是*gettext *包的成员,它默认预安装在大多数现代 Linux 发行版中。

** envsubst可以替换 shell 格式字符串中的环境变量。**它的语法非常简单:

envsubst <TEMPLATE_FILE

上面的命令会将输出打印到 Stdout。如果需要,我们可以将输出重定向到一个文件:

envsubst <TEMPLATE_FILE >OUTPUT_FILE

为简单起见,我们将在本教程中将输出打印到 Stdout。

4.1. 环境变量替换

接下来,让我们在模板文件上测试envsubst命令,看看它是否能产生预期的结果:

$ envsubst <template.txt 
Hey kent,
Nice to see you on this server!
Your home directory is: /home/kent
# If you like using Java, the Java home is: /usr/lib/jvm/default
Also, you can use command substitution: $(YOUR_CMD)

太好了,它完成了工作!正如我们在上面的输出中看到的,所有变量都已被它们的值替换。

值得一提的是,出于安全原因,envsubst会忽略由 shell 完成的替换,例如${variable-default}*、  $(command-list)command-list*

4.2. Shell 脚本中的非环境变量替换

如果我们查看我们的模板文件,我们可能会意识到到目前为止它只包含环境变量,例如*$USER$HOME*。

实际上,我们的模板可以有非环境变量。让我们在模板文件中再添加一行:

$ cat template.txt
Hey $USER,
...
Also, you can use command substitution: $(YOUR_CMD)
If you have any problem, please contact our admin: <$ADMIN1> or <$ADMIN2>.

如我们所见,我们在模板中引入了两个新变量:$ADMIN1和*$ADMIN2*。它们不是环境变量。

此外,在现实世界中,我们可能会创建一个脚本来自动从模板生成消息。

现在,让我们创建一个简单的脚本并声明两个“ ADMIN ”变量:

$ cat gen.sh
#!/bin/bash
eric@server.com
kevin@server.com
envsubst <template.txt

接下来,让我们测试我们的脚本:

$ ./gen.sh 
Hey kent,
Nice to see you on this server!
Your home directory is: /home/kent
# If you like using Java, the Java home is: /usr/lib/jvm/default
Also, you can use command substitution: $(YOUR_CMD)
If you have any problem, please contact our admin: <> or <>.

如我们所见,所有环境变量都已替换为它们的值。但是两个“ ADMIN ”变量是空的。

这是因为envsubst只适用于环境变量

解决该问题的方法是export 这两个变量,使它们可用于从该 shell 创建的所有子进程:

$ cat gen.sh
#!/bin/bash
export eric@server.com
export kevin@server.com
envsubst <template.txt
$ ./gen.sh 
Hey kent,
...
If you have any problem, please contact our admin: <eric@server.com> or <kevin@server.com>.