改进 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 脚本可以接受参数作为输入。以下是一些示例。但首先,您应该理解,良好的编程意味着我们不仅仅编写可以完成我们想要的操作的应用程序;我们必须编写不能完成我们想要的操作的应用程序。我想确保在没有参数的情况下,脚本不会执行任何破坏性操作。因此,这是第一个检查 y。条件检查参数的数量 $# 是否为零,如果为真,则终止脚本。

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 Development Kit (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 程序的起点,然后对其进行测试。

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 移植时出现的问题更少。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.