12.1 什么是 Shell scripts
Shell Script = 针对 shell 写的”剧本”程序——将一连串 shell 指令写进纯文本文件,一次执行。类似 DOS .bat,但更强大:变量、条件判断、循环、函数,不需要编译直接执行。
12.1.1 干嘛学习 shell scripts
| 用途 | 说明 |
|---|---|
| 自动化管理 | 每天自动分析日志、监控流量、备份数据——让系统自己干活 |
| 追踪与管理系统 | CentOS 6.x 以前 /etc/init.d/* 全是 scripts;CentOS 7 虽改用 systemd,但很多服务脚本仍是 shell |
| 入侵检测 | 定时分析登录文件,异常时自动封锁 IP、通知管理员 |
| 连续指令单一化 | 把一长串指令写进一个文件,一键执行 |
| 简易数据处理 | 配合 awk/sed 处理数据——鸟哥每学期用 script 自动建 100+ 学生账号 |
shell script 处理数值运算的速度不如 C/Java——它适合系统管理,不适合大量科学计算。
12.1.2 第一支 script 的撰写与执行
#!/bin/bash # Shebang——声明用哪个 shell,必须第一行!
# Program: This program shows "Hello World!" in your screen.
# History: 2015/07/16 VBird First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "Hello World! \a \n"
exit 0标准结构:① Shebang (#!/bin/bash) → ② 注释说明 → ③ 环境变量宣告(PATH 最重要)→ ④ 主程序 → ⑤ exit N 回传值。
执行方式:
| 方式 | 说明 |
|---|---|
sh script.sh / bash script.sh | 子程序执行(只需 r 权限) |
./script.sh | 子程序执行(需 rx 权限) |
source script.sh / . script.sh | 父程序执行——变量/cd 会保留在当前 shell |
12.1.3 撰写 shell script 的良好习惯创建
每个 script 头部必须记录:功能、版本、作者、版权、History;在重要的程序码旁加注解;使用巢状缩排([Tab] 对齐)。建议用 vim(而非 vi)撰写——vim 有语法检验。
12.2 简单的 shell script 练习
12.2.1 简单范例
对谈式——read 读取输入:
read -p "Please input your first name: " firstname
echo -e "\nYour full name is: ${firstname} ${lastname}"随日期变化——date 创建不同文件名:
date1=$(date --date='2 days ago' +%Y%m%d)
file1=${filename}${date1}
touch "${file1}"数值运算——$(()) 与 bc:
total=$((${firstnu}*${secnu})) # 整数运算(推荐 $(()) 方式)
echo "123.123*55.9" | bc # 小数运算用 bc
echo "scale=${num}; 4*a(1)" | bc -lq # bc 计算 π(scale=小数位数)12.2.2 script 的执行方式差异(source, sh script, ./script)
| 方式 | bash 环境 | 变量是否保留 |
|---|---|---|
sh script.sh / ./script.sh | 新的子程序 bash | ❌ 执行完就消失 |
source script.sh / . script.sh | 当前父程序 bash | ✅ 保留 |
这就是为什么改完
~/.bashrc后要用source ~/.bashrc而不能用bash ~/.bashrc。
12.3 善用判断式
12.3.1 利用 test 指令的测试功能
test -e /dmtsai && echo "exist" || echo "Not exist"| 类别 | 常用标志 | 说明 |
|---|---|---|
| 文件类型 | -e / -f / -d | 是否存在 / 是否为文件 / 是否为目录 |
| 文件权限 | -r / -w / -x | 是否可读 / 写 / 执行 |
| 文件比较 | -nt / -ot / -ef | 是否更新 / 更旧 / 同一 inode |
| 整数比较 | -eq / -ne / -gt / -lt / -ge / -le | = / ≠ / > / < / ≥ / ≤ |
| 字串判断 | -z / -n / == / != | 是否为空 / 非空 / 相等 / 不等 |
| 多重条件 | -a (and) / -o (or) / ! (not) |
12.3.2 利用判断符号 [ ]
[ ] 与 test 完全等价,但两端必须有空格:
[ -z "${HOME}" ]; echo $? # 判断变量是否为空
[ "${yn}" == "Y" -o "${yn}" == "y" ] && echo "OK, continue"⚠️ 三大铁律:① 每个元件间必须有空格;② 变量用双引号括起来(
"${var}");③ 常数用单/双引号。
# ❌ 错误——变量有空格会被拆成多个参数
name="VBird Tsai"; [ ${name} == "VBird" ] # bash: too many arguments
# ✅ 正确
[ "${name}" == "VBird" ]12.3.3 Shell script 的默认变量(1…)
/path/to/scriptname opt1 opt2 opt3 opt4
$0 $1 $2 $3 $4
| 变量 | 含义 |
|---|---|
$0 | 脚本文件名 |
$1~$9 | 第 1~9 个参数;${10} = 第 10 个 |
$# | 参数总个数 |
$@ | 所有参数("$1" "$2" ...各自独立) |
$* | 所有参数(合并为 "$1 $2 ...") |
shift | 参数左移 N 个(shift 1 去掉 1 3) |
12.4 条件判断式
12.4.1 利用 if … then
# 单层
if [ 条件判断式 ]; then
指令段落
fi
# 单层含 else
if [ 条件 ]; then
成立时的指令
else
不成立时的指令
fi
# 多重条件
if [ 条件一 ]; then
指令一
elif [ 条件二 ]; then
指令二
else
都不成立时的指令
fi实战——检测网络服务端口(利用 netstat -tuln 检测 80/22/21/25):
testfile=/dev/shm/netstat_checking.txt
netstat -tuln > ${testfile}
testing=$(grep ":80 " ${testfile})
if [ "${testing}" != "" ]; then
echo "WWW is running in your system."
fi实战——计算退伍日期:用 date --date="YYYYMMDD" +%s 转为秒数 → 时间差 → 换算天数。
12.4.2 利用 case … esac 判断
适用于多个固定值的变量判断:
case ${1} in
"one")
echo "Your choice is ONE"
;;
"two")
echo "Your choice is TWO"
;;
*)
echo "Usage ${0} {one|two}"
;;
esac每个分支以
;;结束;*是默认分支(通配任意);这比多个 if/elif 简洁得多。CentOS 6.x 的/etc/init.d/*脚本广泛使用 case 语法来处理start/stop/restart参数。
12.4.3 利用 function 功能
function fname() {
程序段落
}
function printit(){
echo "Your choice is ${1}" # 这里的 $1 是传给函数的参数!
}
# 调用
printit 1 # 输出 "Your choice is 1"⚠️ function 必须在调用前定义(shell script 从上到下执行)。函数内
$0/$1/$2… 是函数的参数,与脚本的$1不同。
12.5 循环(loop)
12.5.1 while do done, until do done(不定循环)
while — 条件成立时循环:
while [ "${yn}" != "yes" -a "${yn}" != "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
doneuntil — 条件成立时停止(与 while 相反):
until [ "${yn}" == "yes" -o "${yn}" == "YES" ]
do
read -p "Please input yes/YES to stop this program: " yn
donewhile 累加——1+2+…+100:
s=0; i=0
while [ "${i}" != "100" ]
do
i=$(($i+1))
s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s" # 505012.5.2 for…do…done(固定循环)
for var in con1 con2 con3 ...
do
程序段($var 依次取 con1, con2, con3...)
done实战——遍历 /etc/passwd 所有账号:
users=$(cut -d ':' -f1 /etc/passwd)
for username in ${users}; do
id ${username}
done实战——Ping 100 台主机(配合 $(seq 1 100) 或 {1..100}):
network="192.168.1"
for sitenu in $(seq 1 100); do # 或 {1..100}
ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
if [ "${result}" == 0 ]; then echo "Server ${network}.${sitenu} is UP."
else echo "Server ${network}.${sitenu} is DOWN."; fi
done12.5.3 for…do…done 的数值处理
for (( 初始值; 限制值; 执行步阶 ))
do
程序段
done# 累加到用户输入的数值
read -p "Input a number: " nu
s=0
for (( i=1; i<=${nu}; i++ )) # i++ = 每次循环 i+1
do
s=$((${s}+${i}))
done
echo "1+2+...+${nu} = ${s}"12.6 shell script 的追踪与 debug
sh -x script.sh # 显示每条指令的实际执行过程(最常用!)
sh -n script.sh # 只检查语法,不执行(类似 dry-run)
-x输出的每行前有+号,表示实际执行的指令——是调试 shell script 的最佳利器。
命令速查表
| 项目 | 说明 |
|---|---|
#!/bin/bash | Shebang,必须第一行 |
exit 0 | 正常退出(0=成功) |
read -p "msg" var | 提示并读取输入 |
$(()) | 整数运算:s=$(($a+$b)) |
$(cmd) | 取得指令输出 |
test -e/-f/-d/-r/-w/-x | 文件/目录/权限判断 |
[ "${a}" == "${b}" ] | 条件判断(空格必须!) |
-a / -o / ! | and / or / not |
$0 $1…$9 $# $@ | 脚本参数/个数/全部参数 |
shift N | 参数左移 N 个 |
if/then/elif/else/fi | 条件分支 |
case ${var} in / ;; / esac | 多值条件(匹配固定字串) |
function fname(){ } | 函数定义 |
while [ ] do done | 不定循环(条件真→执行) |
until [ ] do done | 不定循环(条件真→停止) |
for var in list do done | 固定循环(遍历列表) |
for (( i=1; i<=10; i++ )) do done | 数值循环 |
$(seq 1 100) / {1..100} | 生成连续序列 |
sh -x script.sh | 调试模式 |
sh -n script.sh | 仅检查语法 |