13 10 月 2026

每天要敲的命令、每周要做的维护、每次部署要走的步骤——这些都该写成脚本。写一次,以后一条命令搞定,还不会漏步骤。

这篇从零讲 Bash 脚本:变量、条件、循环、函数、参数处理,以及能直接用的实战模板。

脚本需要一台能跑的服务器来练。财富云计算 提供云服务器;内网服务、测试环境可选 NAT 型(无独立公网 IP,价格比带独立 IP 机型省一半以上)。

一、第一个脚本

nano hello.sh
#!/bin/bash
echo "Hello, World!"
chmod +x hello.sh
./hello.sh

关键点:

  1. 第一行 #!/bin/bash 是 shebang,指定解释器,必须有
  2. chmod +x 加执行权限
  3. ./hello.sh 执行(不能只写 hello.sh,除非在 PATH 里)

二、变量

#!/bin/bash

# 定义变量(等号两边不能有空格!)
NAME="服务器"
COUNT=10

# 使用变量
echo "你好,$NAME"
echo "数量: ${COUNT}"

# 命令结果赋值给变量
DATE=$(date +%F)
FILES=$(ls /var/www)
TOTAL=`wc -l < file.txt`      # 反引号(老写法)

# 只读变量
readonly PI=3.14

# 删除变量
unset NAME

常见错误:NAME = "x"(等号两边有空格会报错)

特殊变量

变量含义
$0脚本名
$1~$9第 1~9 个参数
$#参数个数
$@所有参数(每个独立)
$*所有参数(作为一个字符串)
$?上一条命令的退出码(0=成功)
$$当前进程 PID
$!最后一个后台进程 PID
#!/bin/bash
echo "脚本名: $0"
echo "第一个参数: $1"
echo "参数个数: $#"
echo "所有参数: $@"
./script.sh a b c
# 脚本名: ./script.sh
# 第一个参数: a
# 参数个数: 3
# 所有参数: a b c

环境变量

export PATH="/usr/local/bin:$PATH"
export LANG=en_US.UTF-8

echo $HOME
echo $USER
echo $PWD

三、输入与输出

# 输出
echo "文本"
echo -n "不换行"
echo -e "支持\t转义\n字符"

# 带颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'      # No Color
echo -e "${GREEN}成功${NC}"
echo -e "${RED}错误${NC}"

# 读取输入
read -p "请输入: " INPUT
echo "你输入了: $INPUT"

# 静默输入(密码)
read -s -p "密码: " PASSWD

四、条件判断

if 语句

if [ 条件 ]; then
    命令
elif [ 条件 ]; then
    命令
else
    命令
fi

文件判断

表达式含义
-f file是普通文件
-d dir是目录
-e path存在
-r file可读
-w file可写
-x file可执行
-s file文件大小 > 0
-z str字符串为空
-n str字符串非空
if [ -f "/etc/nginx/nginx.conf" ]; then
    echo "配置文件存在"
fi

if [ ! -d "/backup" ]; then
    mkdir -p /backup
fi

字符串比较

if [ "$NAME" = "root" ]; then        # 相等(注意空格)
if [ "$NAME" != "root" ]; then       # 不等
if [ -z "$INPUT" ]; then             # 为空

数值比较

if [ $COUNT -eq 10 ]; then     # 等于
if [ $COUNT -ne 10 ]; then     # 不等于
if [ $COUNT -gt 10 ]; then     # 大于
if [ $COUNT -ge 10 ]; then     # 大于等于
if [ $COUNT -lt 10 ]; then     # 小于
if [ $COUNT -le 10 ]; then     # 小于等于

逻辑运算

if [ -f "$F" ] && [ -r "$F" ]; then     # 与
if [ "$A" = "x" ] || [ "$B" = "y" ]; then   # 或
if ! [ -f "$F" ]; then                   # 非

# 或用双括号(更强大,推荐)
if [[ -f "$F" && -r "$F" ]]; then
if [[ "$STR" == a* ]]; then              # 支持通配符
if [[ "$STR" =~ ^[0-9]+$ ]]; then        # 支持正则

case 语句

case "$1" in
    start)
        echo "启动"
        ;;
    stop)
        echo "停止"
        ;;
    restart)
        echo "重启"
        ;;
    *)
        echo "用法: $0 {start|stop|restart}"
        exit 1
        ;;
esac

五、循环

for 循环

# 遍历列表
for i in 1 2 3 4 5; do
    echo $i
done

# 范围
for i in {1..10}; do
    echo $i
done

# 遍历文件
for file in /var/log/*.log; do
    echo "处理 $file"
done

# C 风格
for ((i=0; i<10; i++)); do
    echo $i
done

# 遍历命令输出
for user in $(cat users.txt); do
    echo $user
done

while 循环

# 计数
COUNT=0
while [ $COUNT -lt 5 ]; do
    echo $COUNT
    COUNT=$((COUNT + 1))
done

# 读文件每一行
while read line; do
    echo "$line"
done < file.txt

# 无限循环
while true; do
    echo "每 5 秒执行一次"
    sleep 5
done

循环控制

break       # 跳出循环
continue    # 跳过本次

六、函数

# 定义
function greet() {
    echo "你好,$1"
}

# 或(推荐写法)
greet() {
    local name=$1        # local 定义局部变量
    echo "你好,$name"
    return 0
}

# 调用
greet "张三"

# 返回值(0-255,用 $? 获取)
greet "李四"
echo "返回码: $?"

# 返回值(字符串,用 echo 输出 + 命令替换)
get_date() {
    echo $(date +%F)
}
TODAY=$(get_date)

七、错误处理(重要)

#!/bin/bash
set -e          # 任何命令失败就退出脚本
set -u          # 使用未定义变量就报错
set -o pipefail # 管道中任一命令失败就失败
set -euo pipefail   # 三个一起用(推荐)

# 或手动检查
if ! command -v nginx > /dev/null; then
    echo "错误: nginx 未安装"
    exit 1
fi

# 检查上一条命令
cp a.txt b.txt
if [ $? -ne 0 ]; then
    echo "复制失败"
    exit 1
fi

# 或者
cp a.txt b.txt || { echo "复制失败"; exit 1; }

清理工作用 trap:

#!/bin/bash
TEMP=$(mktemp)
trap "rm -f $TEMP" EXIT      # 脚本退出时(无论正常还是出错)清理

echo "工作中..." > $TEMP
# 中间即使出错退出,临时文件也会被删

八、实用技巧

1)命令替换与算术

# 算术运算
echo $((1 + 2))
echo $((COUNT * 2))
let "COUNT++"

2)字符串处理

STR="hello world"
echo ${#STR}                  # 长度:11
echo ${STR:0:5}               # 截取:hello
echo ${STR/world/bash}        # 替换:hello bash
echo ${STR^^}                 # 转大写
echo ${STR^^}                 # 转大写(Bash 4+)

3)检查命令是否存在

command -v nginx > /dev/null 2>&1 && echo "已安装" || echo "未安装"

4)检查是否为 root

if [ "$(id -u)" != "0" ]; then
    echo "请用 root 运行"
    exit 1
fi

5)日志输出函数

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

log "开始备份"
log "备份完成"

6)调试

bash -x script.sh          # 显示每条命令执行过程
# 或在脚本里
set -x                     # 开启调试
# 某段代码
set +x                     # 关闭调试

7)检查语法

bash -n script.sh          # 只检查语法,不执行

建议装 shellcheck(更专业的检查工具):

apt install shellcheck -y
shellcheck script.sh

九、实战模板

模板一:备份脚本

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/backup"
SRC_DIR="/var/www/html"
DB_NAME="mydb"
DB_USER="root"
KEEP_DAYS=30

log() {
    echo "[$(date '+%F %T')] $*"
}

# 检查目录
mkdir -p "$BACKUP_DIR"

DATE=$(date +%F)

# 备份数据库
log "开始备份数据库..."
mysqldump -u"$DB_USER" --single-transaction "$DB_NAME" \
    | gzip > "$BACKUP_DIR/db-$DATE.sql.gz"

# 备份文件
log "开始备份文件..."
tar -czf "$BACKUP_DIR/files-$DATE.tar.gz" "$SRC_DIR" 2>/dev/null

# 清理旧备份
log "清理 ${KEEP_DAYS} 天前的备份..."
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +$KEEP_DAYS -delete
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +$KEEP_DAYS -delete

# 检查大小
SIZE=$(du -sh "$BACKUP_DIR" | cut -f1)
log "备份完成,总大小: $SIZE"

模板二:服务管理脚本

#!/bin/bash
set -euo pipefail

SERVICE="nginx"

case "${1:-}" in
    start)
        systemctl start $SERVICE
        ;;
    stop)
        systemctl stop $SERVICE
        ;;
    restart)
        systemctl restart $SERVICE
        ;;
    status)
        systemctl status $SERVICE
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

模板三:健康检查脚本

#!/bin/bash

URL="https://example.com"
EXPECT=200

CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$URL" || echo "000")

if [ "$CODE" != "$EXPECT" ]; then
    echo "[$(date)] 异常: $URL 返回 $CODE" >> /var/log/health.log
    # 可选:自动重启服务
    # systemctl restart nginx
    exit 1
fi

echo "[$(date)] 正常" >> /var/log/health.log

模板四:批量处理脚本

#!/bin/bash
set -euo pipefail

# 批量压缩目录下所有 jpg
for img in /var/www/html/uploads/*.jpg; do
    [ -e "$img" ] || continue
    echo "处理: $img"
    jpegoptim --max=85 "$img"
done

echo "全部处理完成"

模板五:带参数的部署脚本

#!/bin/bash
set -euo pipefail

usage() {
    echo "用法: $0 -e <环境> [-b <分支>]"
    echo "  -e  环境: prod | test"
    echo "  -b  分支,默认 main"
    exit 1
}

ENV=""
BRANCH="main"

while getopts "e:b:" opt; do
    case $opt in
        e) ENV=$OPTARG ;;
        b) BRANCH=$OPTARG ;;
        *) usage ;;
    esac
done

if [ -z "$ENV" ]; then
    usage
fi

echo "部署环境: $ENV"
echo "分支: $BRANCH"

case $ENV in
    prod)
        DIR="/var/www/prod"
        ;;
    test)
        DIR="/var/www/test"
        ;;
    *)
        echo "未知环境: $ENV"
        exit 1
        ;;
esac

cd "$DIR"
git pull origin "$BRANCH"
echo "部署完成"

十、脚本放哪、怎么定时

# 个人脚本
~/bin/

# 系统级脚本
/usr/local/bin/

# 加执行权限并可直接调用
chmod +x /usr/local/bin/myscript.sh
myscript.sh

定时执行:

crontab -e
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

详见《crontab 计划任务完全指南》。


十一、常见错误

错误原因解决
Permission denied没执行权限chmod +x
command not found(手动能跑)cron 环境 PATH 不同用绝对路径
syntax error near unexpected token括号/引号不匹配bash -n 检查
变量为空等号两边有空格,或没 $检查语法
[ : integer expression expected数值比较用了字符串确认是数字
脚本执行一半退出遇到错误加 set -e 明确行为
中文乱码编码问题export LANG=en_US.UTF-8
Windows 编辑的脚本报错CRLF 换行符dos2unix script.sh
bad interpretershebang 路径错改成 #!/bin/bash 或用 #!/usr/bin/env bash

十二、进阶方向

掌握基础后可以学:

  • awk / sed:强大的文本处理,见《sed 与 awk 文本处理实战》
  • 正则表达式:配合 grep/sed/awk
  • expect:自动交互(自动输入密码等)
  • 并行处理:xargs -P、GNU parallel
  • 单元测试:bats(Bash 测试框架)

写好脚本能省下大量重复劳动。财富云计算 的云服务器提供完整 root 权限,可自由部署脚本与定时任务;内网服务、测试环境可选 NAT 型(无独立公网 IP,价格比带独立 IP 机型省一半以上),适合长期挂自动化任务。

相关阅读:

  • 《crontab 计划任务完全指南》
  • 《Linux 常用命令速查表》
  • 《服务器备份与快照策略》
延伸阅读
  1. 网站环境怎么选:LNMP 手动 / 宝塔面板 / Docker / 云开发
  2. 问卷表单系统搭建:SurveyKing / Formbricks,数据自己收
  3. 图床搭建:Lsky Pro / Lychee,自托管图片托管方案
  4. 邮件列表搭建:listmonk 自托管 Newsletter,把读者握在自己手里
  5. 文档站点搭建:MkDocs / VuePress / Docsify,产品文档与技术手册方案
  6. RSS 阅读器搭建:FreshRSS 自托管,把信息流拿回自己手里
  7. 短链接服务搭建:Shlink 自托管,链接数据自己掌握
  8. 网站状态监控页搭建:Uptime Kuma,服务器挂了第一时间知道
更多阅读
  1. 上一篇:开源程序部署后的安全检查:WordPress、面板、Docker 镜像的风险
  2. 下一篇:Vim 编辑器使用指南:从退不出去到改配置得心应手
发表评论 1