- 支持试读
第1章:引言
Shell 是一个命令解释器。它不仅是操作系统内核和用户之间的绝缘层,更是一种功能强大的编程语言。Shell 程序(也称为脚本 )是一种易于使用的工具,它通过将系统调用、工具、实用程序和编译后的二进制文件 “粘合”在一起, 来构建应用程序。几乎所有 UNIX 命令、实用程序和工具都可以通过 Shell 脚本调用。不仅如此,Shell 内部命令(例如测试和循环结构)还为脚本赋予了额外的功能和灵活性。Shell 脚本尤其适用于管理系统任务和其他不需要复杂复杂编程语言的例行重复性任务。 - 支持试读
第2章:以 Sha-Bang 引出序幕
这里的 Sha-Bang 是计算机领域的专用术语,指脚本开头第一行的 `#! `符号(由 `# (Sharp)` 和 `! (Bang)` 缩写而来)。在中文技术文档中,通常音译为“释伴”或者直接保留英文。这个标题极具幽默感,因为它双关了英文短语 "Starting off with a bang"(轰轰烈烈地开始)。
第2章:以 Sha-Bang 引出序幕
- 5
- 2026-05-06 10:53:03
这里的 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 scriptname 或 bash 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变量中。系统级可用性:移动到此处后,你不再需要输入路径前缀(如
./),系统会自动在后台找到它。
