定位:Bash 内置核心命令,用来配置 Shell 的执行行为、开关各种特性,是脚本调试、提升脚本健壮性的必备工具。 核心原则:减号 - 开启选项,加号 + 关闭选项(新手最容易记反)


一、基础认知

1. 本质

set 是 Bash 内置命令,不是外部程序,用来修改 Shell 运行时的参数开关。 写在脚本开头会作用于整个脚本;也可以在终端临时执行,只作用于当前 Shell 会话。

2. 基础语法

# 开启某个特性
set -选项名

# 关闭某个特性
set +选项名

# 查看所有当前生效的设置
set -o

❗ 高频易错:- 是开启,+ 是关闭,和常规命令参数逻辑相反。


二、核心高频选项(运维脚本必掌握)

第一类:调试排错专用

1. set -x 开启执行跟踪(最常用调试工具)

  • 作用:脚本执行每一行命令前,先打印这行命令的展开后内容(变量替换完成、通配符展开后的真实命令)。
  • 价值:可以直观看到变量到底替换成了什么值、命令有没有拼接错误,是定位脚本诡异问题的神器。
  • 对应场景:你之前遇到的变量拼写错误、括号不匹配、路径拼接错误,加 -x 一眼就能定位。

示例

#!/bin/bash
set -x
port=9000
echo "检测端口 $port"
ss -tlnp | grep ":$port"

执行后会输出:

+ port=9000
+ echo '检测端口 9000'
检测端口 9000
+ ss -tlnp
+ grep :9000

带 + 号的行就是 set -x 打印的真实执行命令。

  • 关闭:set +x
  • 等价用法:不用改脚本,直接调试执行:bash -x script.sh

第二类:生产脚本健壮性三件套(强烈推荐所有运维脚本开头加上)

2. set -e 遇错即停

  • 作用:脚本中任意一条命令返回非 0 退出码,脚本立即终止执行,不会继续往下跑。
  • 价值:防止错误累积扩大。比如前面创建目录失败,后面还继续往目录里写文件,最终造成更难排查的故障。

坑点(非常重要): 默认对管道命令不生效。例如:

cmd1 | cmd2 | cmd3

管道的退出码默认取最后一个命令的退出码,只要 cmd3 成功,哪怕 cmd1、cmd2 失败了,set -e 也不会触发终止。

解决:配合 set -o pipefail 一起使用。

3. set -u 未定义变量报错

  • 作用:使用了没有定义的变量时,脚本直接报错退出,而不是把变量当成空字符串静默执行。
  • 价值:捕获变量名拼写错误。比如你把 port 写成 prot,不加 -u 会当成空值继续跑,出现各种诡异结果;加了 -u 直接报错定位。

示例

#!/bin/bash
set -u
echo $prot  # 变量名拼错,直接报错:prot: unbound variable

4. set -o pipefail 管道错误捕获

  • 作用:管道中任意一个命令执行失败,整个管道的退出码就为失败。
  • 价值:配合 set -e 使用,让管道里的错误也能触发脚本终止,不会被掩盖。

✅ 生产脚本标准开头(严格模式):

```

!/bin/bash

set -euo pipefail
```

这三行组合,能规避 80% 以上的脚本静默错误。


第三类:其他实用选项

5. set -n 语法检查

  • 作用:只检查脚本语法,不实际执行任何命令。
  • 场景:写完脚本先做语法预检,相当于编译检查。
bash -n script.sh  # 等价于脚本里写 set -n

6. set -f 关闭通配符展开

  • 作用:禁用 * ? 等通配符的自动展开,当成普通字符串处理。
  • 场景:处理包含通配符的文本、避免路径意外展开。

三、易错点汇总(错题笔记)

  1. ❌ 记反开关:认为 + 开启、- 关闭 ✅ 纠正:- 开启特性,+ 关闭特性。
  2. ❌ 以为 set -e 能捕获所有错误 ✅ 纠正:默认对管道无效,必须搭配 set -o pipefail 才能完整覆盖。
  3. ❌ set -u 会影响所有变量 ✅ 纠正:$#``$@``$? 等特殊内置变量不会触发未定义报错。
  4. ❌ 以为 set 是外部命令 ✅ 纠正:Bash 内置命令,用 help set 查看帮助,没有 man set。
  5. ❌ 脚本中间改了 set 不恢复 ✅ 纠正:临时关闭特性后,用完记得重新开启,比如:
set +u
# 这里执行允许空变量的逻辑
set -u

四、结合你的巡检脚本示例

以你之前的端口监控脚本为例,加上严格模式和调试开关:

#!/bin/bash
set -euo pipefail

# 调试时解开下面注释
# set -x

log(){
    echo "[$(date '+%H:%M:%S')] [$1] $2"
}

check_port(){
    local port="$1"
    if ss -tlnp | grep -q ":$port ";then
        log "run" "$port 正在监听"
        return 0
    else
        log "error" "$port 未监听"
        return 1
    fi
}

ports=(9000 9001 9002)
for p in "${ports[@]}"
do
    check_port "$p"
done

五、速记口诀

减开加关别记反, x 调试看展开, e 遇错就停下来, u 防变量没定义, pipefail 管道错误逮, 生产开头三件套,euo pipefail 少不了。

🔗 相关笔记