大家好!我是大聪明-PLUS!

根据模板准备配置文件是一项非常常见的系统管理任务。实现此任务的方法有很多种,每种方法都有其自身的优势。本文将介绍如何使用 shell 脚本来完成此操作。
▍ 学习任务
我们将以解决一个典型问题为例进行研究,该问题需要创建一个 shell 脚本来生成包含节点网络堆栈设置的配置文件。我们假设待配置的节点只有一个名为"ens33"的网络接口。该脚本必须允许指定静态 IP 地址、子网掩码和默认网关。
基于以上考虑,我们得到以下配置文件模板:
|----------------------------------------------------------------------------------------------|
| 自动 ens33 接口 ens33 inet 静态 地址IP_ADDRESS_VALUE 子网掩码NETMASK_VALUE 网关DEFAULT_GATEWAY_VALUE |
为了进一步举例说明,我们假设 IP 地址设置为 192.168.0.10,子网掩码设置为 255.255.255.0,默认网关设置为 192.168.0.1。
▍ 选项 1. 将模板放置在脚本中,使用标准工具生成输出
解决这个问题的最简单方法(新手开发者经常使用的方法)是将模板放在脚本主体中,并使用局部变量作为占位符值。
#!/bin/bash`
`IP="192.168.0.10"`
`NETMASK="255.255.255.0"`
`GATEWAY="192.168.0.1"`
`TEMPLATE="$(`
`cat << EOF`
`auto ens33`
`iface ens33 inet static`
`address $IP`
`netmask $NETMASK`
`gateway $GATEWAY`
`EOF`
`)"`
`echo` `-e` `"$TEMPLATE"`
`
脚本的运行结果(以及所有后续脚本的运行结果)将如下所示:
|-----------------------------------------------------------------------------|
| 自动 ens33 接口 ens33 inet 静态 地址 192.168.0.10 子网掩码 255.255.255.0 网关 192.168.0.1 |
这个方案各方面都很好,唯一的不足之处在于,修改模板时可能出现的人为错误(例如拼写错误或其他错误)会导致脚本"崩溃"。因此,更合理的架构方案是将脚本和模板分别放在不同的文件中。
细心的读者可能会注意到,我们在脚本主体中设置了变量值,这意味着要生成具有不同设置的下一个配置文件,仍然需要修改脚本。没错,确实如此,但这是为了简化操作而有意为之。当然,我们完全可以扩展脚本,例如,添加一个用户界面,让用户可以通过控制台输入变量值。
▍ 选项 2. 将模板放在外部文件中,并使用 envsubst 填充它
让我们创建一个名为"template.txt"的模板文件,内容如下:
|------------------------------------------------------------|
| 自动 ens33 接口 ens33 inet 静态 地址 IP 子网掩码 NETMASK 网关 $GATEWAY |
#!/bin/bash`
`export` `IP="192.168.0.10"`
`export` `NETMASK="255.255.255.0"`
`export` `GATEWAY="192.168.0.1"`
envsubst < template.txt`
▍ 选项 3. 将模板放在外部文件中,并使用 sed 工具填充它。
该工具sed允许您将字符串相互替换。为了避免重写模板,我们将搜索与变量名(IP、NETMASK、GATEWAY)对应的字符串,并将其替换为相应的值。请注意,要搜索字符串"IP",我们需要转义""字符,并搜索字符串"\\IP"(其他变量也适用相同的规则)。
#!/bin/bash`
`IP="192.168.0.10"`
`NETMASK="255.255.255.0"`
`GATEWAY="192.168.0.1"`
`cat` template.txt | `sed` `"s/\$IP/$IP/"` |
`sed` `"s/\$NETMASK/$NETMASK/"` |
`sed` `"s/\$GATEWAY/$GATEWAY/"
▍ 选项 4. 将模板放在外部文件中,并使用 awk 填充它
这个选项在概念上与前一个选项类似,只是sed我们用代替awk,这会稍微影响脚本列表。
#!/bin/bash`
`IP="192.168.0.10"`
`NETMASK="255.255.255.0"`
`GATEWAY="192.168.0.1"`
`cat` template.txt |
`awk` `-v` `replace_str="$IP"` `'{ gsub( /\$IP/ , replace_str ); print }'` |
`awk` `-v` `replace_str="$NETMASK"` `'{ gsub( /\$NETMASK/ , replace_str ); print }'` |
`awk` `-v` `replace_str="$GATEWAY"` `'{ gsub( /\$GATEWAY/ , replace_str ); print }'
▍ 选项 5. 将模板放在外部文件中,并使用 eval 函数填充它
此选项与之前的选项不同之处在于,它不使用任何外部工具,而只使用内置命令eval。用于生成模板的脚本eval如下所示:
#!/bin/bash`
`IP="192.168.0.10"`
`NETMASK="255.255.255.0"`
`GATEWAY="192.168.0.1"`
`TEMPLATE=$(< template.txt)`
eval `"echo -e \"$TEMPLATE\""
你可能想知道我们为什么需要它eval?这个例子会帮助你理解:
#!/bin/bash`
`VAR="A"`
`TEMPLATE_LOCAL="VAR=$VAR"`
`TEMPLATE_FILE="$( < template_file.txt)"`
`# VAR=$VAR`
`echo` `-e` `"$TEMPLATE_LOCAL"`
`# VAR=A`
`echo` `-e` `"$TEMPLATE_FILE"`
`# VAR=$VAR`
eval `"echo -e \"$TEMPLATE_FILE\""`
`# VAR=A
如您所见,eval这使您能够克服 Shell 的一些特性,这些特性会导致脚本对同一数据的解释因数据是在脚本主体中指定还是从外部文件加载而有所不同。现在让我们来谈谈使用此功能提供的特性和功能eval。
首先需要注意的是,需要在模板文件中转义特殊字符。例如,`" " "` 应替换为 `" \"`。
使用此功能的第二个特性eval是能够在模板中编写代码。这使您可以构建复杂的模板,这些模板不仅可以替换值,还可以根据指定的条件输出(或不输出)整个部分。
为了演示此功能,我们将通过添加一个可选的第二个接口设置"ens37"来使我们的教程更加复杂。这意味着"ens33"将始终配置,而"ens37"仅在必要时配置。使用后,eval配置文件模板(eval_template.txt)将如下所示:
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| auto ens33 iface ens33 inet static address ENS33_IP netmask ENS33_NETMASK gateway ENS33_GATEWAY ( if [[ (-n {ENS37_IP}) \&\& (-n {ENS37_NETMASK}) ]]; then cat << EOF auto ens37 iface ens37 inet static address ENS37_IP netmask ENS37_NETMASK EOF fi ) |
我们将对之前编写的脚本进行一些关于变量名称的细微修改。除此之外,所有内容基本保持不变。
#!/bin/bash`
`ENS33_IP="192.168.0.10"`
`ENS33_NETMASK="255.255.255.0"`
`ENS33_GATEWAY="192.168.0.1"`
`ENS37_IP="10.0.0.10"`
`ENS37_NETMASK="255.255.0.0"`
`TEMPLATE=$(< eval_template.txt)`
eval `"echo -e \"$TEMPLATE\""
脚本运行结果:
|-----------------------------------------------------------------------------------------------------------------------------------------|
| 自动配置 ens33 接口 ens33 inet 静态 地址 192.168.0.10 子网掩码 255.255.255.0 网关 192.168.0.1 自动配置 ens37 接口 ens37 inet 静态 地址 10.0.0.10 子网掩码 255.255.0.0 |
除了潜在的风险之外,在模板中编写代码也存在风险。攻击者可以秘密修改模板,添加恶意代码,这些代码会在生成下一个配置文件时执行。你非但无法通过自动化日常任务来简化工作,反而会面临清理黑客攻击后果的棘手问题。
在上面的例子中,模板文件非常小,可以快速扫描(经过适当的训练)。但如果模板很大,几乎不可能找到恶意代码。而且,不断检查模板是否存在未经授权的修改本身就是一个糟糕的主意。
▍ 选项 6. 将模板放在外部文件中,并使用 Bash 的内置功能填充它
另一个纯粹的 Shell 变体,其理念与awk或类似。只是这里我们使用shell的内置工具sed进行字符串搜索和替换。
#!/bin/bash`
`IP="192.168.0.10"`
`NETMASK="255.255.255.0"`
`GATEWAY="192.168.0.1"`
`TEMPLATE="$(< template.txt)"`
`TEMP="${TEMPLATE/\$NETMASK/$NETMASK}"`
`TEMP="${TEMP/\$IP/$IP}"`
`TEMP="${TEMP/\$GATEWAY/$GATEWAY}"`
`echo` `-e` `"$TEMP"
▍ 结论
我们已经了解了六种使用 Shell 脚本构建配置文件的方法。你应该选择哪一种呢?当然是选择你最喜欢的!
在其他条件相同的情况下,可以考虑envsubst使用 `shell` 脚本。它解决了基本问题,而且使用起来非常简洁。但是,出于eval安全考虑,最好避免使用它。