第一个脚本
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 讲 if 和 for,还有 awk 的基本用法——后天那个巡检脚本的所有零件,明天就齐了。