前言

会写 Bash 脚本,是运维和后端开发日常效率的分水岭。这篇把最核心的语法要素串一遍,看完能写出规范、能跑的脚本。

一、脚本骨架

从一个标准骨架开始:

#!/usr/bin/env bash
# 描述: 演示脚本骨架
set -euo pipefail          # 出错即停 + 未定义变量报错 + 管道错误传递

LOG_FILE="/var/log/myapp.log"

log() {
    echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"
}

main() {
    log "脚本开始"
    # ... 业务逻辑
    log "脚本结束"
}

main "$@"

三件套说明:

  • set -e:任何命令返回非零就立即退出
  • set -u:使用了未定义变量直接报错
  • set -o pipefail:管道中任何一个命令失败,整条管道算失败

二、变量与引号

name="world"                 # 等号两边不能有空格!
echo "hello, $name"          # 双引号: 会展开变量
echo 'hello, $name'          # 单引号: 原样输出
echo "文件数: $(ls | wc -l)" # 命令替换
echo "当前目录: $(pwd)"

引号三原则:

  1. 变量引用一律加双引号:"$name",防止空格和通配符展开把脚本搞崩
  2. 原样字符串用单引号
  3. 命令替换用 $() 而不是反引号 ` `(可读性、可嵌套性更好)

特殊变量速查:

变量 含义
$0 脚本名
$1~$9 第 1~9 个参数
$# 参数个数
$@ 所有参数(每个独立)
$? 上一条命令的退出码
$$ 当前脚本 PID

三、算术运算

count=10
count=$((count + 1))
echo "$count"                       # 11

# 自增
((count++))

# 浮点运算 bash 原生不支持, 借助 bc
echo "scale=2; 10 / 3" | bc         # 3.33

四、条件判断

if 语法(注意分号和空格)

if [ -f /etc/passwd ]; then
    echo "文件存在"
elif [ -d /etc ]; then
    echo "这是目录"
else
    echo "都不满足"
fi

常用测试条件:

写法 含义
-f file 是普通文件
-d dir 是目录
-e path 存在
-z "$s" 字符串为空
-n "$s" 字符串非空
"$a" = "$b" 字符串相等
-eq / -ne / -gt / -lt 数值 比较
-r / -w / -x 可读/可写/可执行

数值比较示例:

load=$(uptime | awk -F'load average:' '{print int($2)}')
if [ "$load" -gt 4 ]; then
    echo "负载偏高: $load"
fi

case 语句

case "$1" in
    start)   echo "starting...";;
    stop)    echo "stopping...";;
    restart) echo "restarting...";;
    *)       echo "用法: $0 {start|stop|restart}"; exit 1;;
esac

五、循环

for 循环

# 遍历列表
for svc in nginx redis mysql; do
    systemctl is-active "$svc" && echo "$svc ✓"
done

# 遍历数字范围
for i in {1..5}; do
    echo "第 $i 次执行"
done

# 遍历命令输出(逐行)
for line in $(cat /etc/passwd); do
    echo "$line"
done

# 更安全的逐行读取(保留空格)
while IFS= read -r line; do
    echo "$line"
done < /etc/hosts

while + break/continue

retry=0
while true; do
    if curl -sf http://localhost:8080/health >/dev/null; then
        echo "服务健康"
        break
    fi
    ((retry++))
    [ "$retry" -ge 10 ] && { echo "重试超限"; exit 1; }
    sleep 3
done

六、实战小例子:批量检查主机存活

#!/usr/bin/env bash
set -euo pipefail

HOSTS=(192.168.1.10 192.168.1.11 192.168.1.12)

for ip in "${HOSTS[@]}"; do
    if ping -c1 -W1 "$ip" &>/dev/null; then
        echo "[UP]   $ip"
    else
        echo "[DOWN] $ip"
    fi
done

小结

  • 骨架 + set -euo pipefail 是规范脚本的起点
  • 变量永远记得 "$var" 双引号
  • 数值判断用 -eq/-gt,字符串判断用 =
  • 复杂逐行处理用 while IFS= read -r

Bash 适合胶水和短脚本;逻辑超过两三百行时,就该考虑 Python 了。


本文是「Linux 基础」系列第 2 篇,下篇预告:Linux 文本处理三剑客 grep、sed、awk。