← 回目录
26 Week 06 · 脚本与收尾

Shell 脚本:变量和判断

最后一周。三天后你会有一个能拿去面试讲的巡检脚本——今天先把地基打好。

第一个脚本

hello.sh
#!/bin/bash
# 这是注释

echo "hello world"
echo "今天是 $(date +%F)"
两种运行方式
$ chmod +x hello.sh        # Day 06 学的,要有 x 权限
$ ./hello.sh               # 方式一:直接执行

$ bash hello.sh            # 方式二:交给 bash 执行(不需要 x 权限)
第一行那个 #! 叫 shebang
作用
告诉系统用哪个解释器来跑这个文件。#!/bin/bash 就是用 bash。
必须在第一行
前面不能有空行或空格,否则不生效。
为什么重要
不写的话,不同环境下可能被不同的 shell 执行,行为不一致。写脚本永远第一行加上它。

变量

三个必须记住的规矩
赋值和取值
NAME="rocky"           # ✅ 等号两边不能有空格!
# NAME = "rocky"       ❌ 会报 command not found

echo $NAME             # 取值要加 $
echo "${NAME}9"        # 加大括号避免歧义(否则变成 $NAME9)
三条规矩
1 · 等号两边不能有空格
这是新手第一个坑。NAME = "x" 会被理解成"执行 NAME 这个命令"。
2 · 取值要加 $
赋值时不加,取值时加。
3 · 变量最好用双引号包起来
echo "$FILE" 而不是 echo $FILE——文件名带空格时不加引号会出大问题。

命令替换:$()

把命令结果存进变量
这个用得极多
TODAY=$(date +%F)              # 2026-09-07
DISK=$(df -h / | awk 'NR==2 {print $5}')
COUNT=$(ls /etc | wc -l)

echo "今天 $TODAY,根分区用了 $DISK,/etc 下有 $COUNT 个文件"

Day 02 学的 cp -a nginx.conf nginx.conf.bak.$(date +%F) 就是这个语法。老写法是反引号 ` `,但 $() 更清晰、能嵌套,现在都用它

引号的区别

面试偶尔会问
双引号 vs 单引号
NAME="rocky"

echo "hello $NAME"      # hello rocky    ← 双引号会解析变量
echo 'hello $NAME'      # hello $NAME    ← 单引号原样输出
怎么选

需要变量展开就用双引号,想要原样字符串就用单引号。

Day 05 里 grep -v "^#" 用双引号、awk '{print $5}' 用单引号——后者必须单引号,否则 $5 会被 shell 先解释掉。

$?:上一条命令成功了吗

脚本判断的基础
0 是成功,非 0 是失败
$ systemctl is-active nginx
active
$ echo $?
0        ← 成功

$ systemctl is-active 不存在的服务
$ echo $?
3        ← 非 0,失败
这里反直觉

0 表示成功,非 0 表示失败——和很多编程语言里 0 代表 false 正好相反。

原因是:成功只有一种,失败有很多种,用不同的非零值区分不同的错误类型

几个特殊变量

脚本参数
#!/bin/bash
echo "脚本名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "参数个数:$#"
echo "所有参数:$@"
运行看看
$ ./args.sh nginx restart
脚本名:./args.sh
第一个参数:nginx
第二个参数:restart
参数个数:2

这让脚本变得通用——不用把服务名写死在脚本里,调用时传进去就行。

简单判断

明天详讲,今天先见个面
用 && 和 || 做最简单的分支
# 成功才执行后面的(Day 02 学过)
systemctl is-active --quiet nginx && echo "nginx 正常"

# 失败才执行后面的
systemctl is-active --quiet nginx || echo "nginx 挂了!"

# 组合起来,像三元表达式
systemctl is-active --quiet nginx \
  && echo "正常" \
  || echo "异常"

--quiet 让它不输出内容,只用返回值说话——写脚本时经常这么用。

三个让脚本更靠谱的习惯

写生产脚本必备
开头加上这行
#!/bin/bash
set -euo pipefail
这三个开关的作用
set -e
任何命令失败就立刻退出,不继续往下跑。避免"前面挂了后面还在执行"造成更大破坏。
set -u
用了未定义的变量就报错退出。防止变量名打错导致空值——rm -rf $DIR/ 里 DIR 是空的话就是灾难。
set -o pipefail
管道里任何一环失败,整条管道就算失败。默认只看最后一个命令的结果,会漏掉前面的错误。
但巡检脚本别用 set -e

巡检脚本的逻辑是"检查很多项,某项失败要记下来然后继续查下一项"。加了 set -e,第一个服务异常脚本就退出了,后面的都不检查了。

第 28 天写巡检脚本时,我们不加 -e,但会用 -u知道什么时候该用、什么时候不该用,比无脑照抄强。

调试技巧

脚本行为不对时
$ bash -x hello.sh        # 打印每一行实际执行的命令
+ echo 'hello world'
hello world
+ date +%F
+ echo '今天是 2026-09-07'

$ bash -n hello.sh        # 只检查语法,不执行 ← 像 nginx -t

bash -x 是调试神器——能看到变量实际被展开成了什么,一眼看出哪一步不对。

今天的面试点

「你写过 shell 脚本吗?」

别答"会一点",那等于没说。答具体的:

「写过巡检脚本,每天定时跑,检查磁盘、内存、关键服务状态,超过阈值就记告警日志。」

后面三天你就把它真写出来——到时候这句话是有底气的。

「单引号和双引号有什么区别?」

双引号会解析变量和命令替换,单引号原样输出。所以 awk '{print $5}' 必须用单引号,不然 $5 会被 shell 先吃掉。」

今天的练习

敲完再走 · 约 15 分钟
  • ~/hello.sh:shebang + echo + $(date +%F)chmod +x 后运行
  • 故意踩坑:写一行 NAME = "test"(等号两边加空格),运行看报什么错,然后改对
  • 试试 echo "hello $NAME"echo 'hello $NAME' 的区别
  • 写个 args.sh 打印 $0 $1 $2 $#,用不同参数调用几次
  • systemctl is-active nginx; echo $?,再敲一个不存在的服务对比返回值
  • bash -x 跑一遍你的脚本,看它把每一行展开成了什么
  • 写一行:systemctl is-active --quiet nginx && echo 正常 || echo 异常
明天预告

Day 27 讲 iffor,还有 awk 的基本用法——后天那个巡检脚本的所有零件,明天就齐了。