第2章:以 Sha-Bang 引出序幕

这里的 Sha-Bang 是计算机领域的专用术语,指脚本开头第一行的 #! 符号(由 # (Sharp)! (Bang) 缩写而来)。在中文技术文档中,通常音译为“释伴”或者直接保留英文。这个标题极具幽默感,因为它双关了英文短语 “Starting off with a bang”(轰轰烈烈地开始)。

在最简单的情况下,脚本无非是存储在文件中的一系列系统命令。至少,这省去了每次调用时重新输入那一连串特定命令的麻烦。

示例2-1. cleanup:一个用于清理 /var/log 目录下日志文件的脚本:

# Cleanup
# 以 root 身份运行

cd /var/log
cat /dev/null > messages
cat /dev/null > wtmp
echo "日志文件清理完毕"

这里没有任何特别之处,仅仅是一组命令的集合,它们本可以同样轻易地在终端中被逐条调用。将这些命令放入脚本的优势,远不止于免去重复输入的劳苦。脚本由此演变成了一个程序——一个工具——并且它可以针对特定的应用场景,被轻易地修改或定制。

示例2-1. cleanup:一个改进后的清理脚本

#!/bin/bash
# Bash 脚本的标准头部,

# Cleanup, 版本 2

# 以 root 身份运行
# 如果不是 root,在此插入打印错误信息并退出的代码

LOG_DIR=/var/log
# 变量优于硬编码值。
cd $LOG_DIR

cat /dev/null > messages
cat /dev/null > wtmp


echo "日志文件清理完毕"

exit	#从脚本中“退出”的正确且恰当的方法。
		#不带参数的单纯 "exit" 将返回前一条命令的退出状态码。

在脚本编写中,**硬编码(Hard-coding)**是指将具体的路径(如 /var/log)或数值直接写死在命令中。使用变量具有以下核心优势:

退出状态码 (Exit Status):在 Linux 中,命令执行成功通常返回 0,失败则返回非零值(1 ~ 255)。

示例2-3. cleanup:上述脚本的增强版与通用版

由于你可能并不希望彻底抹除所有的系统日志,这个版本的脚本保留了消息日志(message log)的最后一部分。在实践中,你会不断发现各种优化既有脚本的方法,从而进一步提升效率。

位于脚本顶部的 Sha-bang (#!) 告知系统:该文件是一组需要交给指定命令解释器执行的指令。#! 实际上是一个双字节魔数(Magic Number)——一种用于标识文件类型的特殊标记,在这里它表示这是一个可执行的 Shell 脚本(关于这个有趣的话题,可以输入 man magic 了解更多详情)。

紧随 Sha-bang 之后的是一个路径名。该路径指向用于解释脚本指令的程序,它可以是某种 Shell、编程语言或某种工具软件。接着,该命令解释器将从脚本顶部(Sha-bang 之后的下一行)开始执行指令,并忽略所有注释。

上述每一行脚本头部都在调用不同的命令解释器,无论是默认 Shell /bin/sh(在 Linux 系统中通常指向 Bash),还是其他解释器。使用 #!/bin/sh(大多数商业 UNIX 变体中的默认 Bourne Shell)可以增强脚本在非 Linux 机器上的可移植性,但代价是必须牺牲 Bash 的特有功能。不过,这样做可以使脚本符合 POSIX sh 标准

请注意,Sha-bang 后面给出的路径必须准确无误,否则运行脚本时,你得到的唯一结果通常只会是一条错误信息——“Command not found”(未找到命令)。

如果脚本仅由一组通用的系统命令组成,且不涉及任何 Shell 内部指令,那么 #! 也可以省略。但在上文的第二个示例中,首行的 #! 是必不可少的,因为变量赋值语句 lines=50 使用了特定于 Shell 的结构。再次提请注意,#!/bin/sh 调用的是默认 Shell 解释器,而在 Linux 机器上,它默认指向 /bin/bash

本教程鼓励采用模块化的方法来构建脚本。建议你留意并收集那些在未来脚本中可能派上用场的“样板”(boilerplate)代码段。久而久之,你就能建立起一个相当丰富且精妙的例程库。

例如,下面这段脚本前导代码(prolog)用于测试脚本在调用时是否包含了正确数量的参数:

E_WRONG_ARGS=85
script_parameters="-a -h -m -z"
#                  -a = 全部, -h = 帮助, 等等。

if [ $# -ne $Number_of_expected_args ]
then
  echo "用法: `basename $0` $script_parameters"
  # `basename $0` 指的是该脚本的文件名。
  exit $E_WRONG_ARGS
fi

  • $#:这是一个特殊变量,代表传递给脚本的参数总数

  • basename $0$0 是脚本执行时的完整路径,通过 basename 命令可以剔除路径前缀,仅保留文件名。这使得脚本在修改文件名后,报错信息依然能自动对齐。

  • 前导代码:这段逻辑通常放在脚本的最开头(Prolog 部分),用于拦截非法输入。

很多时候,你会为了完成某个特定任务而编写脚本,本章开篇的脚本就是一个例子。随后,你可能会想到将该脚本通用化,以便处理其他类似的任务。将字面量(即“硬编码”常量)替换为变量是实现这一目标的第一步,而将重复的代码块替换为函数则是更进一步。

2.1 调用脚本

编写完脚本后,你可以通过 sh scriptnamebash scriptname 来调用它。(不推荐使用 sh <scriptname,因为这会由于重定向导致脚本内部无法从标准输入 stdin 读取数据。)更方便的做法是通过 chmod 命令使脚本本身直接变为可执行文件:

chmod 555 scriptname # 赋予全部用户读取/执行权限

或者:

chmod +rx scriptname # 赋予全部用户读取/执行权限
chmod u+rx scriptname # 只赋予脚本所属用户读取/执行权限

使脚本具有可执行权限后,你现在可以通过 ./scriptname 来测试它了。如果脚本以 “sha-bang” 行开头,那么调用该脚本时,系统会自动调用正确的命令解释器来运行它。

作为最后一步,在完成测试和调试后,你可能希望将其移动到 /usr/local/bin 目录下(当然,需要以 root 身份操作),从而使该脚本成为一个系统级的可执行文件,供你自己及所有其他用户使用。届时,只需在命令行中输入 scriptname 并回车,即可调用该脚本。

  • /usr/local/bin:这是 Linux 存放用户自建全局工具的标准目录,通常默认就在系统的 $PATH 变量中。

  • 系统级可用性:移动到此处后,你不再需要输入路径前缀(如 ./),系统会自动在后台找到它。

要查看完整内容,请先登录