5个方法助你行云流水写脚本

3,190 阅读4分钟

作为一名有理想的程序员,我们或多或少都会写一些 shell 脚本,它们可能很短,只有简单的几个命令,也可能非常长,包含了系统检查、编译和运行等多个庞大复杂的任务

在安装软件的时候,你一定见过很多名为configureshell 脚本,这个脚本可以保证软件不会破坏当前系统,并且拥有可以正常安装的环境配置,实现这个脚本需要很多的逻辑和技巧。作为一名使用了多年 shell 脚本的老司机,我积累了大量关于 shell 脚本的使用技巧来提高脚本编写效率,今天,我将其中的一些精华分享出来,希望能对一些人有所帮助

初次接触脚本

刚开始的时候,我的脚本就是一系列的命令,像流水账一样,一般用来节省写多个标准 shell 语句的时间。比如发布一个 web 网站,其中一个任务就是把静态文件解压到 nginx 目录,脚本大概长这样

$ cp static_file.tar.gz /usr/nginx/home/
$ cd /usr/nginx/home/
$ tar -zxvf static_file.tar.gz

这个脚本当然节省了很多的时间,但从长远来看,这并不是一个很有用的脚本。过了一段时间,我学会了一些其他的方式来写脚本,并用来完成更有挑战性的任务,比如创建一个软件包,安装软件或者备份一个文件服务器等

1. 条件语句

就像其他的变成语言一样,条件判断是一个易用且强大的语法。条件语句可以让我们的程序按照一定的逻辑执行。我的每个脚本几乎都用到了条件语句

最基本的条件语句使用 if 标记。if 语句可以对一些条件进行判断,我们可以根据判断的结果来决定脚本的走向。例如,我们可以检查 java bin 目录是否存在,如果存在,就代表 java 已经正常安装,然后我们可以利用脚本,将 java 的运行目录放到 path 环境变量中,这样我们就可以在任意一个文件目录下运行 java 命令了

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

if 条件判断还有一些高级的用法,比如我想判断一个目录是否存在,不存在则创建

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

2. 执行权限控制

也许我们会想让脚本只能由特定的用户执行。尽管 linux 已经有了用户和组的权限控制,但还是想让脚本来控制。比如可以使用脚本来控制只能是当前 web 容器的所有者才能执行,或者只有 root 用户才能执行。linux系统给我们提供了两个环境变量去实现这个功能,其中一个是 $USER ,获取当前用户名。另一个是 $UID 获取当前用户的唯一标记

普通用户

在一个多用户,多应用的环境中,下面的示例展示了如何限制,只有 apple 这个用户才可以执行这个脚本。 if 语句可以理解为向用户提问:"执行的用户是不是 不是apple ?"。 如果发现执行人就是 apple,第一个 echo 就会被执行,后边紧跟着一个 exit 1,意味着结束脚本执行

if [ "$USER" != 'apple' ] ; then
    echo "此脚本只能由 apple 执行"
    exit 1
fi
echo "继续执行脚本"

ROOT 用户

第二个例子展示了,只有 root 用户才能执行此脚本。因为 UID0 的就是 root 用户,我们可以在判断时使用 -gt 扩展选项来禁止所有 UID 大于 0 的用户继续执行脚本

if [ "$UID" -gt 0 ] ; then
    echo "此脚本只能由 root执行"
    exit 1
fi
echo "继续执行脚本"

使用参数

和任何可以执行的文件一样,脚本文件也可以接受参数。下边会展示一些参数传递的例子。但一定要注意,要写好代码,不仅要让程序执行我们想要执行的任务,也要保证程序不要执行我们不想执行的任务。我一般在脚本中会确保在用户没有参数传递进来的时候不做任何事情。因此,我写脚本的第一件事就是使用 $# 检查脚本的参数是否传递

if [ $# -eq 0 ] ; then
    echo "参数错误"
    exit 1
fi
echo "获得了参数: $#"

多个参数

可以在命令行中传递多个参数给脚本, 脚本中对于用户提供的变量提供了很简单的获取方式,以 $ 符号开头,按照参数递增,例如 $1 $2 $3 等。 下边的例子是打印用户输入的三个参数。当然,我们还必须要注意根据参数传递的数量做不同的处理。

echo $1 $2 $3

看了上面的代码,你也许会问:是不是跳过了 $0?

嗯。。。是的,因为 $0 在脚本中也是一个非常有用的变量,他代表我们执行的脚本的名称

echo $0

对于这个参数的一个非常重要的应用就是在脚本中生成一个包含脚本名称的日志。最简单的例子大概如下

echo test >> $0.log

上边只是最简单使用,一般在使用时需要加上一些逻辑判断,比如判断文件位置是否可写等

4. 用户输入

另外一个非常有用的功能就是在脚本执行的时候让用户输入一些信息,如下

echo "请输入一个字母: "
    read word
    echo $word

这样就可以让用户在执行脚本的时候进行判断

read -p "是否安装此软件? [Y/n]: " answ
    if [ "$answ" == ’n' ] ; then
        exit 1
    fi
        echo "开始安装软件"

异常退出

几年之前,我写了一个脚本去安装最新版本的 jdk ,这个脚本的基本流程就是解压 jdk 包到指定的目录,更新 jdk 的软链,然后使用一些其他的方式让系统识别这个新版本。这就需要考虑一个问题,如果我解压 jdk 包失败了,继续执行脚本就会破坏我之前安装的 jdk 环境。所以我的脚本一定要能解决这种问题,也就是说只有解压成功才继续执行脚本,大概的源码如下

tar kxzmf jdk-8-linux-x64.tar.gz -C /home/apple/jdk --checkpoint=.500; ec=$?
if [$ec -ne 0 ] ; then
    echo "安装 jdk 失败"
    exit 1
fi

识别系统命令执行出错最简单的方法时使用 $? 变量,如下例

ls T; ec=$?; echo $ec

我们如果先执行 touch T 然后执行上边的脚本,ec 会是 0。然后删除 T 文件,调用 rm T 。再次执行脚本,ec 会变成 2,因为 T 文件没有找到,系统会抛出一个为 2 的错误标记

总结

在一些稍微复杂写的场景,我们可能就会想要用一些比较高级的语言实现,类似 Python C 或者 Java。其实很时候完全没有必要。shell 脚本非常强大,有很多值得学习和使用的东西。我希望通过上边的简单介绍能让刚刚使用 shell 并准备放弃的人了解到 一点 shell 的强大及魅力