Shell 脚本自动化实战:从单行命令到可复用脚本

举报
yd_232564628 发表于 2026/10/12 11:37:28 2026/10/12
【摘要】 前面几篇已经覆盖了 find、tar、grep、sed、awk、管道和重定向,但这些工具单独使用时,每次都要重新敲一遍。Shell 脚本的价值就在于把一次性操作变成可重复、可分享、可调度的自动化流程。它不需要学习新语言——脚本里写的就是你平时在终端敲的那些命令,只是加上了变量、条件判断和循环。 最基础的脚本创建一个文件 backup.sh:#!/bin/bash# 备份指定目录到 /tmp/...

前面几篇已经覆盖了 find、tar、grep、sed、awk、管道和重定向,但这些工具单独使用时,每次都要重新敲一遍。Shell 脚本的价值就在于把一次性操作变成可重复、可分享、可调度的自动化流程。它不需要学习新语言——脚本里写的就是你平时在终端敲的那些命令,只是加上了变量、条件判断和循环。

最基础的脚本

创建一个文件 backup.sh:

#!/bin/bash

# 备份指定目录到 /tmp/backup
tar -czf /tmp/backup_$(date +%Y%m%d).tar.gz /home/user/documents
echo "备份完成"

赋予执行权限并运行:

chmod +x backup.sh
./backup.sh

第一行的 #!/bin/bash 叫 shebang,告诉系统用哪个解释器执行这个脚本。# 开头的是注释,$(date +%Y%m%d) 是命令替换,会先执行 date 命令再嵌入结果。

实际使用中,高频场景与模式

1. 变量与参数

#!/bin/bash

# 自定义变量
BACKUP_DIR="/tmp/backup"
DATE=$(date +%Y%m%d)

# 接收命令行参数
SOURCE_DIR=$1       # 第1个参数
RETENTION_DAYS=$2   # 第2个参数,默认保留天数

# 参数默认值(如果没传则用默认值)
RETENTION_DAYS=${2:-7}

echo "备份目录: $BACKUP_DIR"
echo "源目录: $SOURCE_DIR"
echo "保留天数: $RETENTION_DAYS"

tar -czf "${BACKUP_DIR}/backup_${DATE}.tar.gz" "$SOURCE_DIR"
./backup.sh /home/user/documents 30

用变量代替硬编码路径,脚本通用性大幅提升。${2:-7} 表示如果第 2 个参数未提供,则默认值为 7。

2. 条件判断

#!/bin/bash

# 检查参数是否提供
if [ -z "$1" ]; then
    echo "用法: $0 <目录路径>"
    exit 1
fi

# 检查目录是否存在
if [ ! -d "$1" ]; then
    echo "错误: 目录 '$1' 不存在"
    exit 1
fi

# 检查是否有足够的磁盘空间(简单示例)
AVAILABLE=$(df /tmp | awk 'NR==2 {print $4}')
if [ "$AVAILABLE" -lt 1048576 ]; then   # 小于 1GB
    echo "警告: /tmp 空间不足"
    exit 1
fi

echo "开始备份..."
tar -czf "/tmp/backup_$(date +%Y%m%d_%H%M%S).tar.gz" "$1"
echo "备份完成"

[ ] 是 test 命令的简写形式,常用判断:

判断 含义
-z "$var" 变量为空
-n "$var" 变量非空
-f "$file" 文件存在且为普通文件
-d "$dir" 目录存在
-e "$path" 路径存在(文件或目录)
"$a" -eq "$b" 数值相等
"$a" -lt "$b" 数值小于
"$a" -gt "$b" 数值大于

3. 循环处理

#!/bin/bash

# 遍历目录中的每个文件
for file in /var/log/*.log; do
    if [ -f "$file" ]; then
        size=$(stat -c%s "$file")
        echo "$file: ${size} bytes"
    fi
done

# 遍历命令行传入的多个参数
for dir in "$@"; do
    echo "处理: $dir"
    tar -czf "${dir##*/}.tar.gz" "$dir"
done

# while 循环读取文件内容
while IFS= read -r line; do
    echo "处理: $line"
done < server_list.txt

"$@" 展开为所有命令行参数(每个参数保持独立),"$*" 则将所有参数合并为一个字符串。遍历文件列表时优先用 "$@"。

4. 函数封装

#!/bin/bash

# 日志函数
log() {
    local level=$1
    shift
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*"
}

# 备份函数
backup_dir() {
    local source=$1
    local dest=$2

    if [ ! -d "$source" ]; then
        log "ERROR" "源目录不存在: $source"
        return 1
    fi

    log "INFO" "开始备份 $source -> $dest"
    tar -czf "$dest" "$source"
    local status=$?

    if [ $status -eq 0 ]; then
        log "INFO" "备份成功"
    else
        log "ERROR" "备份失败,退出码: $status"
    fi
    return $status
}

# 主逻辑
backup_dir "/home/user/documents" "/tmp/backup.tar.gz"
backup_dir "/etc/nginx" "/tmp/nginx_backup.tar.gz"

函数让脚本结构清晰,local 声明局部变量避免污染全局命名空间,return 返回退出码供调用方判断。

5. 错误处理与安全选项

#!/bin/bash
set -euo pipefail

# -e: 任何命令返回非零退出码时立即退出
# -u: 使用未定义的变量时报错
# -o pipefail: 管道中任一命令失败则整个管道失败

# 捕获错误并清理
cleanup() {
    echo "发生错误,清理临时文件..."
    rm -f /tmp/temp_backup_*
}
trap cleanup ERR

# 正常执行
temp_file="/tmp/temp_backup_$$"
cp important_data "$temp_file"
process "$temp_file"
mv "$temp_file" final_output

set -euo pipefail 应成为每个严肃脚本的标配。trap 可以在脚本退出(正常或异常)时执行清理操作,$$ 是当前进程 ID,用于生成唯一的临时文件名。

6. 结合前面学过的工具

#!/bin/bash
set -euo pipefail

LOG_DIR="/var/log/nginx"
REPORT="/tmp/nginx_report_$(date +%Y%m%d).txt"

{
    echo "=== Nginx 日志分析报告 ==="
    echo "生成时间: $(date)"
    echo ""

    echo "--- 访问量最高的 10 个 IP ---"
    awk '{print $1}' "$LOG_DIR"/access.log.* | sort | uniq -c | sort -rn | head -10

    echo ""
    echo "--- 各状态码分布 ---"
    awk '{print $9}' "$LOG_DIR"/access.log.* | sort | uniq -c | sort -rn

    echo ""
    echo "--- 404 错误最多的 URL ---"
    awk '$9 == "404" {print $7}' "$LOG_DIR"/access.log.* | sort | uniq -c | sort -rn | head -10

    echo ""
    echo "--- 日志文件总大小 ---"
    du -sh "$LOG_DIR"/access.log.*
} > "$REPORT"

echo "报告已生成: $REPORT"

用 { ... } > file 将一组命令的输出统一重定向到文件,避免每条命令都写一次 >>。

7. 定时任务(cron)

# 编辑 crontab
crontab -e

# 每天凌晨 2 点执行备份
0 2 * * * /home/user/scripts/backup.sh /home/user/documents /tmp/backups

# 每小时检查一次磁盘空间
0 * * * * /home/user/scripts/check_disk.sh

# 每周一清理 7 天前的日志
0 3 * * 1 find /var/log -name "*.log" -mtime +7 -delete

cron 格式:分 时 日 月 星期 命令。脚本中建议使用绝对路径,因为 cron 执行时环境变量很少。

8. 调试技巧

# 语法检查(不执行)
bash -n script.sh

# 逐步跟踪执行(显示每条命令及其参数)
bash -x script.sh

# 在脚本中局部开启调试
set -x          # 开启调试
some_command    # 这行会被打印出来
set +x          # 关闭调试

bash -x 输出中 + 开头的行是实际执行的命令,非常适合排查"为什么变量值是空的"这类问题。

如果不知道如何下手,可以先尝试

#!/bin/bash
echo "当前时间: $(date)"
echo "当前用户: $(whoami)"
echo "当前目录: $(pwd)"
echo "磁盘使用:"
df -h /

保存为 info.sh,chmod +x info.sh,然后 ./info.sh 运行。确认能正常输出后,逐步加入变量、参数和条件判断。

或者,写一个简单的清理脚本:

#!/bin/bash
set -euo pipefail

TARGET_DIR=${1:-/tmp}
DAYS=${2:-7}

echo "清理 $TARGET_DIR 中 ${DAYS} 天前的临时文件..."
find "$TARGET_DIR" -type f -name "*.tmp" -mtime +"$DAYS" -delete
echo "清理完成"

脚本编写最佳实践

  • 始终用 set -euo pipefail:让错误尽早暴露,而不是悄悄继续执行。
  • 变量加双引号:"$var" 而不是 $var,防止空格和通配符导致意外展开。
  • 用绝对路径:脚本被 cron 或其他程序调用时,工作目录可能不是预期的。
  • 关键操作先预览:删除、移动、覆盖类操作,先用 echo 打印会执行什么,确认无误后再真正执行。
  • 退出码有意义:成功返回 0,失败返回非零值,方便被其他脚本调用时判断。
  • 写注释和用法说明:三个月后你自己也会忘记这个脚本是干嘛的。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。