改进 Bash 脚本的 5 种方法

了解 Bash 如何帮助您应对最具挑战性的任务。
168 位读者喜欢这篇文章。
A graduate degree could springboard you into an open source job

Opensource.com

系统管理员经常编写 Bash 脚本,有些脚本很短,有些脚本很长,以完成各种任务。

您是否看过软件供应商提供的安装脚本?他们通常会添加大量函数和逻辑,以确保安装正常进行,并且不会对客户的系统造成损害。多年来,我积累了各种增强 Bash 脚本的技术,我想分享其中的一些,希望能对其他人有所帮助。以下是为说明这些简单示例而创建的小型脚本集合。

入门

当我刚开始时,我的 Bash 脚本只不过是一系列命令,通常旨在节省标准 shell 操作的时间,例如部署 Web 内容。其中一项任务是将静态内容提取到 Apache Web 服务器的主目录中。我的脚本大致如下:

cp january_schedule.tar.gz /usr/apache/home/calendar/
cd /usr/apache/home/calendar/
tar zvxf january_schedule.tar.gz

虽然这节省了我一些时间和打字时间,但从长远来看,这肯定不是一个非常有趣或有用的脚本。随着时间的推移,我学习了使用 Bash 脚本来完成更具挑战性的任务的其他方法,例如创建软件包、安装软件或备份文件服务器。

1. 条件语句

与许多其他编程语言一样,条件语句一直是一个强大且常用的功能。条件语句使计算机程序能够执行逻辑。我的大多数示例都基于条件逻辑。

基本条件语句使用“if”语句。这使我们能够测试某些条件,然后我们可以使用这些条件来操纵脚本的执行方式。例如,我们可以检查 Java bin 目录是否存在,这将表明已安装 Java。如果找到,则可以使用该位置更新可执行路径,以启用 Java 应用程序的调用。

if [ -d "$JAVA_HOME/bin" ] ; then
    PATH="$JAVA_HOME/bin:$PATH"

2. 限制执行

您可能希望限制脚本只能由特定用户运行。尽管 Linux 具有用于用户和组的标准权限以及用于启用此类保护的 SELinux,但您可以选择在脚本中放置逻辑。也许您想确保只有特定 Web 应用程序的所有者才能运行其启动脚本。您甚至可以使用代码将脚本限制为 root 用户。Linux 有几个环境变量,我们可以在此逻辑中进行测试。其中一个是 $USER,它提供用户名。另一个是 $UID,它提供用户的标识号 (UID),对于脚本,则提供执行用户的 UID。

用户

第一个示例显示了在具有多个应用程序服务器实例的多主机环境中,我如何将脚本限制为用户 jboss1。条件“if”语句本质上是在询问:“执行用户是否不是 jboss1?”当条件为真时,将调用第一个 echo 语句,然后调用 exit 1,这将终止脚本。

if [ "$USER" != 'jboss1' ]; then
     echo "Sorry, this script must be run as JBOSS1!"
     exit 1
fi
echo "continue script"

Root

下一个示例脚本确保只有 root 用户才能执行它。由于 root 的 UID 为 0,因此我们可以在条件 if 语句中使用 -gt 选项来禁止所有大于零的 UID。

if [ "$UID" -gt 0 ]; then
     echo "Sorry, this script must be run as ROOT!"
     exit 1
fi
echo "continue script"

3. 使用参数

就像任何可执行程序一样,Bash 脚本可以接受参数作为输入。以下是一些示例。但首先,您应该理解,良好的编程意味着我们不仅要编写满足我们需求的应用程序;我们还必须编写不能做我们想做的事情的应用程序。我喜欢确保脚本在没有参数的情况下不会执行任何破坏性操作。因此,这是我要做的第一个检查。该条件检查参数数量 $# 是否为零,如果为真,则终止脚本。

if [ $# -eq 0 ]; then
    echo "No arguments provided"
    exit 1
fi
echo "arguments found: $#"

多个参数

您可以将多个参数传递给脚本。脚本用于引用每个参数的内部变量只是递增的,例如 $1$2$3 等等。我将使用以下行扩展上面的示例,以回显前三个参数。显然,需要额外的逻辑来根据总数正确处理参数。此示例仅为演示起见而简化。

echo $1 $2 $3

在讨论这些参数变量时,您可能想知道,“他跳过零了吗?”

嗯,是的,我跳过了,但我有一个很好的理由!确实存在 $0 变量,它非常有用。它的值只是正在执行的脚本的名称。

echo $0

在执行期间引用脚本名称的一个重要原因是生成一个日志文件,该日志文件的名称中包含脚本的名称。最简单的形式可能只是一个 echo 语句。

echo test >> $0.log

但是,您可能需要添加更多代码,以确保将日志写入到具有您认为对您的用例有帮助的名称和信息的位置。

4. 用户输入

在脚本中使用的另一个有用功能是它在执行期间接受输入的能力。最简单的方法是向用户提供一些输入。

echo "enter a word please:"
 read word
 echo $word

这也允许您向用户提供选择。

read -p "Install Software ?? [Y/n]: " answ
 if [ "$answ" == 'n' ]; then
   exit 1
 fi 
   echo "Installation starting..."

5. 失败时退出

几年前,我编写了一个脚本,用于在我的计算机上安装最新版本的 Java 开发工具包 (JDK)。该脚本将 JDK 存档解压缩到特定目录,更新符号链接,并使用 alternatives 实用程序使系统了解新版本。如果 JDK 存档的解压缩失败,继续操作可能会破坏系统范围内的 Java。因此,我希望脚本在这种情况下中止。我不希望脚本进行下一组系统更改,除非存档已成功解压缩。以下是该脚本的摘录:

tar kxzmf jdk-8u221-linux-x64.tar.gz -C /jdk --checkpoint=.500; ec=$?
if [ $ec -ne 0 ]; then
     echo "Installation failed - exiting."
     exit 1
fi

让您演示 $? 变量用法的一种快速方法是使用以下简短的单行代码:

ls T; ec=$?; echo $ec

首先,运行 touch T,然后运行此命令。ec 的值将为 0。然后,删除 Trm T,并重复该命令。ec 的值现在将为 2,因为 ls 报告错误情况,因为找不到 T

您可以利用此错误报告来包含逻辑,就像我在上面所做的那样,以控制脚本的行为。

总结

我们可能会认为我们需要使用 Python、C 或 Java 等语言来实现更高的功能,但这不一定是真的。Bash 脚本语言非常强大。有很多东西需要学习才能最大限度地发挥其用处。我希望这些简单的示例能够阐明使用 Bash 进行编码的潜力。

接下来阅读什么

创建 Bash 脚本模板

在本系列的第二篇文章中,创建一个相当简单的模板,您可以将其用作其他 Bash 程序的起点,然后对其进行测试。

(通讯员)
2019 年 12 月 19 日
Alan Formy-Duval Opensource.com Correspondent
Alan 拥有 20 年的 IT 经验,主要在政府和金融领域。他最初是一名增值经销商,之后转行从事系统工程。Alan 的背景是高可用集群应用程序。他在 Oracle Press/McGraw Hill 的《Oracle Solaris 11 系统管理》一书中撰写了“用户和组”和“Apache 和 Web 堆栈”章节。

8 条评论

很棒的文章!

“if”语句在所有 Bash 脚本中都非常方便。对于非常短的“if”语句,&& 和 || 快捷方式也很有用。例如,当我需要确保目录存在时,我经常这样做

[ -d "$1" ] || mkdir --parents "$1"

该语句仅在目录不存在时才创建目录。您也可以这样编写语句

[ ! -d "$1" ] && mkdir --parents "$1"

&& 表示“如果测试为真,则执行下一个语句”。|| 表示“如果测试为假,则执行下一个语句”。您可以将它们视为执行级别的“AND”和“OR”。

我更喜欢更新后的 test 命令:[[。它比 [ 有很多改进。

此外,我认为更新您的比较技术以采用更新的 bash 习语会很有用。不再需要使用 -eq。这就可以正常工作:if [[ 1 == 1 ]]; then echo "Yes, 1 equals 1."; fi

使用 env 变量检查用户在我看来有点危险。我宁愿使用:if [[ $( id -nru ) != 'renich' ]]; then exit 1; fi

仅是我的个人意见。

由于本文的标题似乎表明人们会对任何和所有使脚本变得更好的方法感兴趣,因此我建议在脚本中添加大量注释,以便将来可以参考这些注释,从而从总体上大幅提高脚本的质量和价值。我经常会浏览旧脚本,看看是否已经编写了一些可以重用的内容,但除非我记录了脚本,否则我并不总是清楚自己是否拥有现有的代码片段。

为此,我建议不仅要写出有关脚本如何工作、脚本的用途或将来如何修改变量的详细信息,而且甚至可以添加一些通用关键字。这有助于在脚本目录中进行常规“grep”类型搜索。

使用 $USER 或 $UID 限制脚本的执行非常不安全。两者都是普通的环境变量。它们应该在 shell 启动时正确初始化,但用户可以将其更改为任何内容。我可以运行

USER=root UID=0 your_script

您的脚本会直接假设我是 root 用户。

如果您想知道用户是谁,而不是用户声称是谁,请使用“/usr/bin/id -u”获取数字用户 ID 或“/usr/bin/id -un”获取用户名。使用“/usr/bin/id”,而不仅仅是“id”;后者允许用户通过调整 $PATH 来替换他们自己的“id”命令。

要确保脚本只能由 root 用户执行,请使用 chown 和 chmod 来确保除了实际的 root 用户之外,没有其他用户可以执行它

chown root:root your_script
chmod 500 your_script

您的大多数变量引用都应包含在双引号中,以避免在变量值包含任何特殊字符时出现错误行为。

在第 5 节中,将 $? 的值保存在 $ec 中是不必要的。直接使用 $? 即可。$? 的值相当脆弱,因此例如

command_that_might_fail
echo "The status was $?"
if [ $? -ne 0 ] ; then
exit 42
fi

将测试 echo 命令的状态。只需注意这个问题,您就可以直接使用 $?,这将使您的代码更易于阅读。

就此而言,甚至更简单的方法是不显式引用 $?。“if”或“while”语句中的条件始终是一个命令,条件是命令成功(将 $? 设置为 0)还是失败(将 $? 设置为 0 以外的任何值)。(是的,“[”是一个命令,通常内置在 shell 中。)因此,您的 tar 命令可以是

if ! tar ... ; then
echo "Installation failed" 1>&2
exit 1
fi

(请注意,我将错误消息打印到文件描述符 1,即标准错误流。)或者您可以使用 || 或 && 运算符

tar ... || {
echo "Installation failed" 1>&2
exit 1
}

Keith,我一直在安全环境中操作,其中“用户”是应用程序或系统帐户,例如 oracle、postgres、tomcat、weblogic 等。只有受信任的管理人员才被允许访问该帐户。但是,话虽如此,您的警告值得关注,因为任何访问该帐户的人员都可以更改变量。感谢您指出这一点。

回复 作者:Keith Thompson(未验证)

学习 Bourne 系列 shell 脚本的用户需要了解历史。

POSIX shell 有自己的标准,并且是一种比 bash 提供的语言小得多的语言。此 shell 在使用 Debian dash 时是相关的,而在使用 Busybox shell 时则不太相关。早于 POSIX 和 ksh88 的标准不应详细研究。请务必查阅正确的参考资料 - HP-UX 实际上为“man sh-posix”命令提供了 ksh88,这是完全错误的。POSIX shell 不实现数组和 [[ 条件结构。

在 POSIX 之前是 Korn shell 的 '88 版本,它引入了 [[ - 许多人对 /bin/[ 程序及其与 /bin/test 的关系感到惊讶。Korn 还弃用了 -gt 运算符,这是在 '93 版本中引入浮点数的一部分。

Bash 借鉴了 Korn 的许多创新,但在几个主题上也有自己的发展方向(主要是协进程)。

我发现 POSIX 标准和 mksh(Android 的默认 shell)中的 Korn 功能是最佳功能集,除非在其他 shell 之一中需要某些特定功能。mksh 比 bash 小得多,并且在从商业 UNIX 移植时出现的问题更少。

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.