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
done

until — 条件成立时停止(与 while 相反):

until [ "${yn}" == "yes" -o "${yn}" == "YES" ]
do
    read -p "Please input yes/YES to stop this program: " yn
done

while 累加——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"     # 5050

12.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
done

12.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/bashShebang,必须第一行
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仅检查语法