写点小脚本:把重复三次以上的操作自动化

判断标准很简单:一件事做了三次以上,就该写成脚本。脚本不需要多复杂,能省下每次敲命令和回忆参数的时间就值。

一、脚本的第一行该写什么

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

这四个选项能挡掉绝大多数低级 bug:

选项作用
set -e任何命令失败就退出(不会带着错误继续跑)
set -u引用未定义变量时报错(而不是当成空字符串)
set -o pipefail管道中任一环失败就算失败
set -x打印每条执行的命令(调试时加)
bash
# 调试时这样跑,能看到每一步
bash -x script.sh

二、范例一:一键部署脚本

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

HOST="baize"
REMOTE_DIR="/var/www/baize"

echo "==> 本地检查"
[ -f index.html ] || { echo "不在项目根目录"; exit 1; }

echo "==> 同步文件(先试跑)"
rsync -avz --delete --dry-run ./ "$HOST:$REMOTE_DIR/"

read -rp "确认部署?[y/N] " ok
[ "$ok" = "y" ] || { echo "已取消"; exit 0; }

rsync -avz --delete ./ "$HOST:$REMOTE_DIR/"

echo "==> 重载 Nginx"
ssh "$HOST" "sudo nginx -t && sudo systemctl reload nginx"

echo "==> 完成 $(date)"

两个值得学的点:

  • read -rp 让用户确认,避免手滑把生产环境覆盖;
  • --dry-run 先看会做什么,确认后再真跑。

三、范例二:每日巡检

bash
#!/usr/bin/env bash
# check.sh —— 早晨花 10 秒看一遍机器

echo "== 负载 $(cat /proc/loadavg | cut -d" " -f1-3)"
echo "== 磁盘"
df -h / | tail -1
echo "== 内存"
free -h | head -2 | tail -1

echo "== 服务状态"
for s in nginx ssh fail2ban; do
  if systemctl is-active --quiet "$s"; then
    echo "  ✓ $s"
  else
    echo "  ✗ $s 未运行!"
  fi
done

echo "== 证书剩余天数"
end=$(echo | openssl s_client -connect www.bzii.cn:443 -servername www.bzii.cn 2>/dev/null \
      | openssl x509 -noout -enddate | cut -d= -f2)
echo "  $(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 )) 天"

四、范例三:批量处理文件

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

# 把目录下所有 .jpg 转 .webp
shopt -s nullglob
for f in *.jpg; do
  out="${f%.jpg}.webp"
  [ -f "$out" ] && { echo "跳过已存在: $out"; continue; }
  cwebp -q 80 "$f" -o "$out"
  echo "转换: $f -> $out"
done

# 批量重命名:把空格换成下划线
for f in *" "*; do
  mv -- "$f" "${f// /_}"
done

shopt -s nullglob 很重要:没匹配到文件时不会把 *.jpg 当字面量处理。

五、范例四:写日志的小工具函数

bash
#!/usr/bin/env bash

log()  { echo "[$(date "+%F %T")] INFO  $*"; }
warn() { echo "[$(date "+%F %T")] WARN  $*" >&2; }
die()  { echo "[$(date "+%F %T")] ERROR $*" >&2; exit 1; }

log "开始备份"
[ -d /var/www ] || die "目录不存在,退出"
warn "磁盘剩余不足 20%"

统一带时间戳,日志才有排查价值。

六、参数处理

简单场景用位置参数就够了:

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

usage() {
  echo "用法: $0 <start|stop|status> [服务名]"
  exit 1
}

action="${1:-}"
svc="${2:-nginx}"

case "$action" in
  start)  sudo systemctl start "$svc" ;;
  stop)   sudo systemctl stop "$svc" ;;
  status) systemctl status "$svc" ;;
  *)      usage ;;
esac

七、常见错误

错误正确做法
路径含空格没加引号一律 "$var"
[ $x = "y" ] 在 x 为空时崩[[ ]]"$x" 加引号
在脚本里用 cd 后相对路径乱了开头记 ROOT=$(cd "$(dirname "$0")" && pwd)
命令找不到cron 里 PATH 很短,用绝对路径
Windows 编辑过sed -i "s/\r$//" script.sh 去掉 CRLF

八、什么时候该换个语言写

脚本超过 100 行、开始出现复杂数据结构、需要处理 JSON/YAML 时,就该换成 Python。 Bash 的舒适区是:串联几条命令 + 简单判断 + 文件操作,超出这个范围别硬撑。

bash
# 处理 JSON 用 jq,不要 sed 硬抠
curl -s https://api.example.com/data | jq -r ".items[] | select(.status=="ok") | .name"