当前位置: 首页 > news >正文

Shell脚本实战:从变量循环到三剑客,搞定Linux自动化运维

这次我们来看一套 Shell 脚本编程实战内容,主题是变量、循环、函数和文本三剑客。网上讲 Shell 的教程很多,但不少是零散命令的拼凑,拿到运维场景里根本连不成一条能跑的链路。这套内容的价值在于:它把编写 Linux 自动化任务最常用的语法和工具串起来了,不是“看懂语法”就结束,而是能直接写出批量处理、日志分析、服务巡检一类的脚本。

先交代清楚目标读者。如果你正在做运维、云计算运维、桌面运维,或者刚入门 Linux 想要一条更高效的学习路径,这篇文章可以直接收藏。正文会从环境准备开始,逐步讲变量、条件判断、循环、函数,再重点拆解 grep、sed、awk 这三个文本处理工具,最后落到批量任务、日志清理、定时任务、API 调用这类真实场景。看完之后,你至少能独立写出一个带参数校验、带日志输出、能定时执行的脚本。

文章不会停留在“列命令”这个层面,而是会给出完整示例、执行思路和常见错误。每段代码都标注了语言,直接复制到你的 Linux 环境就能跑。涉及生产环境的部分,会明确给出使用边界,避免把一条没验证的脚本丢到线上机器里造成事故。

1. 核心能力速览

能力项说明
适用平台Linux / Unix 环境,主流发行版均可
编程语言Bash / Shell
核心内容变量、条件判断、循环、函数、grep/sed/awk 三剑客
启动方式Bash 解释器直接执行,无需安装额外框架
硬件要求无 GPU 需求,纯 CPU 环境即可
是否支持批量任务支持,适合批量文件处理、日志分析、服务巡检、备份清理
是否支持 API 接入可通过 curl 等命令调用 HTTP 接口
是否支持定时调度支持,配合 cron 定时任务
学习成本较低,掌握基础命令后即可上手
适合场景Linux 运维、自动化测试、日志分析、云计算运维、桌面运维

从应用场景看,Shell 脚本是 Linux 环境下最直接的自动化手段。不需要安装庞大的运行环境,只要系统里有 Bash,就能写、能跑、能接到 cron 里做周期任务。

2. 适用场景与使用边界

先说适合做什么。日常运维里有大量重复操作,比如检查多台服务器的存活状态、清理过期日志、批量重命名文件、提取日志里的 IP 和状态码、统计接口响应时间,这些都可以用 Shell 脚本快速完成。Shell 的哲学是“把命令串起来”,所以它特别适合处理文本、文件和进程类任务。

再说哪些场景不建议用 Shell。如果遇到非常复杂的 JSON 数据处理、多线程并发计算、图形界面程序、大规模分布式任务调度,更稳妥的选择是 Python、Go 或专门的编排工具。Shell 脚本一旦写得过于复杂,调试成本会明显上升,不如直接用更现代的语言维护。

使用边界要特别注意三件事:

第一,不要在未测试的情况下直接在生产环境运行脚本。Shell 脚本里很多命令是不可逆的,例如rm -rf、重定向覆盖文件、批量执行远程命令,先在小范围或测试机上验证。

第二,涉及批量远程操作、端口扫描、数据抓取时,确保你有目标主机或系统的合法授权。未授权的扫描、爆破、批量探测可能违反安全合规要求。

第三,不要在脚本里硬编码明文密码、密钥或敏感数据。如果必须调用凭据,优先使用环境变量、加密配置或密钥管理工具。日志输出也要避免包含敏感信息。

Shell 脚本是运维利器,但越强的工具越要控制使用边界。写脚本之前先想清楚:这个任务是否适合 Shell、会不会影响现有服务、失败后如何回滚。

3. 环境准备与前置条件

Shell 脚本开发对硬件要求非常低,你只需要一台安装了 Linux 的机器或虚拟机,即使是云服务器、树莓派、Windows WSL 也都可以。现在常见的 Linux 发行版如 Ubuntu、Debian、CentOS、Rocky Linux 都自带 Bash。

先确认当前环境的 Shell 类型和版本:

echo $SHELL bash --version

正常情况下输出里会显示/bin/bash/usr/bin/bash,以及 Bash 的版本号。如果系统中同时存在 zsh、sh 或其他 Shell,本文的脚本统一按照 Bash 语法编写。

开发 Shell 脚本不需要专门的 IDE,但推荐使用支持语法高亮的编辑器,比如 vim、nano、VS Code。如果是远程服务器,直接用 vim 编辑最方便。

检查系统里是否具备常用命令:

which grep sed awk curl tar crontab

这些命令在大多数发行版中都是默认安装的。如果提示缺少某个命令,可以根据系统包管理器安装。例如:

# Ubuntu / Debian sudo apt update sudo apt install -y grep sed gawk curl cron # CentOS / Rocky / RHEL sudo yum install -y grep sed gawk curl cronie

创建一个专门存放脚本的目录,避免散落在用户目录或系统目录:

mkdir -p ~/shell-practice cd ~/shell-practice

这个目录就是我们的实验区。后续创建的脚本都放在这里,保持文件整洁,也方便测试权限和路径。

第一个脚本很简单,验证环境是否可用:

#!/bin/bash echo "Hello, Shell" echo "当前 Shell: $SHELL" echo "当前用户: $(whoami)"

写入文件后,需要通过chmod添加执行权限,再运行:

chmod +x ~/shell-practice/first.sh ~/shell-practice/first.sh

看到三行输出,说明环境完全正常。

4. Shell 变量与基础语法

4.1 变量定义与引用

Shell 变量不需要声明类型,赋值时注意等号两边不能有空格:

#!/bin/bash name="alice" age=26 echo "姓名: $name" echo "年龄: ${age}岁"

变量名由字母、数字、下划线组成,不能以数字开头。使用$变量名${变量名}引用变量,推荐使用花括号写法,便于在拼接字符串时明确边界。

Shell 还支持只读变量:

#!/bin/bash readonly VERSION="1.0.0" VERSION="2.0.0" # 这里会报错

4.2 特殊变量

脚本执行时,系统会传入一些特殊变量,它们在自动化脚本中非常常用:

特殊变量含义
$0当前脚本的文件名
$1$2第 1、2 个位置参数
$#参数个数
$@所有参数列表
$?上一条命令的退出码,0 表示成功
$$当前进程 PID

写一个脚本演示:

#!/bin/bash echo "脚本名称: $0" echo "第一个参数: $1" echo "第二个参数: $2" echo "参数数量: $#" echo "所有参数: $@" echo "上一条命令退出码: $?"

执行测试:

bash args.sh hello world

输出会依次显示脚本名、传入的两个参数、参数数量和退出码。这类特殊变量在写带参脚本时是基础中的基础。

4.3 字符串处理

字符串拼接和截取是脚本开发中最常见的操作之一:

#!/bin/bash str="hello, linux shell" echo "长度: ${#str}" echo "截取前5个字符: ${str:0:5}" echo "截取从第7个字符开始: ${str:7}" echo "替换第一个 linux 为 Linux: ${str/linux/Linux}" echo "替换所有 l 为 L: ${str//l/L}"

4.4 算术运算

Bash 内置的算术运算使用$(( )),不要直接写1 + 1让它计算,那会被当作字符串:

#!/bin/bash a=10 b=3 echo "加法: $((a + b))" echo "减法: $((a - b))" echo "乘法: $((a * b))" echo "除法取整: $((a / b))" echo "取余: $((a % b))"

如果脚本里有比较复杂的数学逻辑,也可以借助awk或 Python,但简单的运算用$(( ))就够。

4.5 用户输入与交互

使用read命令读取用户输入,适合写成半交互式的运维脚本:

#!/bin/bash read -p "请输入要检查的服务名: " service_name echo "你输入的是: $service_name"

如果要实现静默输入,比如输入密码,可以加-s参数:

read -s -p "请输入密码: " password echo "" echo "密码长度: ${#password}"

4.6 命令替换

命令替换是把命令的输入结果保存到变量里,这是 Shell 自动化的核心能力之一:

#!/bin/bash current_date=$(date +%Y-%m-%d) current_dir=$(pwd) echo "今天日期: $current_date" echo "当前目录: $current_dir"

旧式写法使用反引号`,不推荐。$( )更清晰,且支持嵌套。例如:

hours=$(($(date +%H) + 0)) echo "当前小时: $hours"

5. 条件判断与流程控制

5.1 if / elif / else

Shell 的条件判断语法要注意方括号前后的空格,少了空格会直接报错:

#!/bin/bash read -p "请输入一个数字: " num if [ "$num" -gt 10 ]; then echo "这个数大于 10" elif [ "$num" -eq 10 ]; then echo "这个数等于 10" else echo "这个数小于 10" fi

数字比较用以下运算符:-eq等于、-ne不等于、-gt大于、-lt小于、-ge大于等于、-le小于等于。

字符串比较使用=!=

#!/bin/bash str1="hello" str2="world" if [ "$str1" = "$str2" ]; then echo "两个字符串相等" else echo "两个字符串不相等" fi if [ -z "$str1" ]; then echo "字符串为空" fi

5.2 文件判断

Linux 运维脚本里大量使用文件判断,例如检查配置文件是否存在、目录是否可写、日志文件是否为空:

表达式含义
-f "$file"是否为普通文件
-d "$dir"是否为目录
-e "$path"路径是否存在
-s "$file"文件是否非空
-r "$file"是否可读
-w "$file"是否可写
-x "$file"是否可执行
#!/bin/bash path="/etc/nginx/nginx.conf" if [ -f "$path" ]; then echo "文件存在" else echo "文件不存在" fi if [ -r "$path" ]; then echo "文件可读" fi

5.3 逻辑运算符

组合多个条件时,用&&表示与、||表示或、!表示非:

#!/bin/bash age=25 city="beijing" if [[ "$age" -ge 18 && "$city" == "beijing" ]]; then echo "条件满足" fi

这里使用了[[ ]],它相比[ ]支持更丰富的语法,比如正则匹配,推荐在 Bash 脚本中优先使用:

#!/bin/bash url="https://example.com/api/v1" if [[ "$url" =~ ^https:// ]]; then echo "以 https 开头" fi

5.4 case 多分支

case 适合处理有限分支场景,比如根据用户传入参数选择执行不同逻辑:

#!/bin/bash action=$1 case "$action" in start) echo "启动服务" ;; stop) echo "停止服务" ;; restart) echo "重启服务" ;; *) echo "用法: $0 {start|stop|restart}" exit 1 ;; esac

case 的写法比一串 if/elif 更清晰,尤其是在写服务管理脚本时非常有效。

5.5 退出码与安全保护

Shell 脚本里exit可以主动退出并返回状态码。后面配合$?可以判断脚本是否成功:

#!/bin/bash if [ ! -d "/var/log/myapp" ]; then echo "目录不存在,创建中" mkdir -p /var/log/myapp if [ $? -ne 0 ]; then echo "创建目录失败" exit 1 fi fi echo "目录准备完成"

另外,在脚本开头加一句set -e,可以让脚本在任何一条命令出错时立即退出,避免错误被忽略;set -u可以检查未定义变量;set -o pipefail可以防止管道中错误被吞掉。推荐在正式脚本中这样写:

#!/bin/bash set -euo pipefail

这行配置相当于给脚本加了一层保护,很多“诡异问题”都能在第一时间暴露出来。

6. 循环结构与批量任务

6.1 for 循环

for 循环可以遍历数字列表、文件列表、命令行参数等。最常见的用法是遍历指定区间:

#!/bin/bash for i in {1..5}; do echo "第 $i 次循环" done

遍历文件列表是运维常用场景:

#!/bin/bash for file in /tmp/*.log; do echo "处理文件: $file" done

批量重命名文件,例如把所有.txt后缀改成.bak

#!/bin/bash for file in *.txt; do mv "$file" "${file%.txt}.bak" echo "重命名: $file -> ${file%.txt}.bak" done

这里使用了变量替换${file%.txt},作用是把后缀.txt去掉。如果文件名中包含空格,必须加引号。

6.2 while 循环

while 循环适合在满足条件时反复执行。最典型的场景是逐行读取文件:

#!/bin/bash while read -r line; do echo "读取到: $line" done < /etc/hostname

也可以用 while 做计数循环:

#!/bin/bash count=1 while [ "$count" -le 5 ]; do echo "count=$count" count=$((count + 1)) done

6.3 无限循环与后台运行

一些巡检或监控类脚本需要持续运行,可以使用无限循环配合sleep控制频率:

#!/bin/bash while true; do echo "$(date +%F\ %T) 监控中,内存使用率: $(free -h | awk '/^Mem/{print $3}')" sleep 5 done

这种脚本不要直接在前台跑,可以用nohup放到后台运行,把输出写入日志:

nohup bash monitor.sh > monitor.log 2>&1 &

6.4 循环控制 break / continue

break 跳出整个循环,continue 跳过当前循环剩余部分进入下一轮:

#!/bin/bash for i in {1..10}; do if [ "$i" -eq 3 ]; then echo "跳过数字 3" continue fi if [ "$i" -eq 7 ]; then echo "到达 7,退出循环" break fi echo "当前数字: $i" done

6.5 循环内使用后台并发

如果有大量批量任务,顺序执行太慢,可以在循环内用&后台执行,再用wait等待所有任务结束:

#!/bin/bash for ip in 192.168.1.1 192.168.1.2 192.168.1.3; do ( ping -c 1 -W 1 "$ip" > /dev/null 2>&1 && echo "$ip 存活" || echo "$ip 不通" ) & done wait echo "所有检查完成"

这种并发写法会同时发起多个 ping,相比逐条执行能明显减少总耗时。但要注意,并发数量不要太多,否则会拖慢系统,生产环境建议限制并发数。

6.6 循环内批量检查服务端口

批量检查多台服务器端口是否开放,结合nc/dev/tcp实现:

#!/bin/bash hosts=("192.168.1.10" "192.168.1.11") port=22 for host in "${hosts[@]}"; do timeout 3 bash -c "echo > /dev/tcp/$host/$port" 2>/dev/null if [ $? -eq 0 ]; then echo "$host:$port 端口正常" else echo "$host:$port 端口不可达" fi done

7. 函数封装与脚本结构

7.1 函数定义与调用

当脚本里有多处重复逻辑时,应该封装成函数。函数需要在调用之前定义:

#!/bin/bash log_info() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" } log_error() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" } log_info "开始执行备份" log_error "备份失败"

7.2 函数参数与返回值

函数内部的$1$2是函数参数,不是脚本参数。返回值用return返回 0 到 255 的整数:

#!/bin/bash check_service() { local service_name=$1 systemctl is-active "$service_name" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "active" return 0 else echo "inactive" return 1 fi } status=$(check_service nginx) echo "nginx 状态: $status"

如果想返回一个字符串,直接用 echo 输出,用变量接收,比 return 更灵活。

7.3 局部变量 local

函数内部定义的变量默认是全局的,容易污染外部环境。使用local声明局部变量是更安全的做法:

#!/bin/bash my_func() { local temp="内部值" echo "函数内: $temp" } my_func echo "函数外访问 temp: $temp"

上面代码最后一行会输出空字符串,因为temp只在函数内有效。

7.4 脚本通用结构

一个正式脚本应该有清晰的阶段划分。下面是一个带参数校验、日志、主函数调用的结构模板:

#!/bin/bash set -euo pipefail # 常量定义 SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) LOG_FILE="$SCRIPT_DIR/script.log" # 日志函数 log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE" } # 参数校验 if [ $# -lt 1 ]; then log "参数不足,用法: $0 <target>" exit 1 fi # 核心处理函数 process() { local target=$1 log "开始处理 $target" # 实际业务逻辑 } # 主函数入口 main() { process "$1" log "全部完成" } main "$1"

这种结构看起来很“标准”,但在真实运维里很实用。日志、参数校验、退出码控制都在最前面,后面改业务逻辑时不用调整外围结构。

8. 三剑客实战:grep、sed、awk

文本三剑客是 Linux 运维处理日志和配置文件的看家工具。单独掌握每个命令不难,难的是把它们串到脚本流程里。

8.1 grep:文本过滤

grep 用来在文件或标准输出中匹配文本行。最常用参数:

参数含义
-E支持扩展正则表达式
-i忽略大小写
-v反向匹配
-c统计匹配行数
-n显示行号
-r递归搜索目录

示例:

# 在 nginx 访问日志中搜索 500 状态码 grep -n ' 500 ' /var/log/nginx/access.log # 统计错误日志行数 grep -c 'ERROR' /var/log/app/error.log # 排除注释行和空行,查看配置文件有效内容 grep -vE '^\s*#|^\s*$' /etc/nginx/nginx.conf

在脚本里,grep 经常配合条件判断使用:

#!/bin/bash if grep -q 'listen 443' /etc/nginx/nginx.conf; then echo "已配置 HTTPS 监听" else echo "未配置 HTTPS 监听" fi

-q参数表示静默模式,不输出内容,只影响退出码,适合在 if 条件中使用。

8.2 sed:流式编辑

sed 可以对文本进行增删改查,不需要用 vim 打开文件,非常适合脚本内自动修改配置。

替换字符串是最常用的操作:

# 把文件中的 old 替换为 new sed 's/old/new/' file.txt # 全局替换并直接写回文件 sed -i 's/old/new/g' file.txt # 在每行末尾追加内容 sed -i 's/$/ 追加内容/' file.txt # 删除匹配行 sed -i '/^#/d' file.txt

sed 里最常见的坑是-i的兼容性。GNU sed 写sed -i没问题,macOS 自带 BSD sed 需要加扩展名,否则会提示undeclared identifier

在脚本里批量修改配置文件非常实用。例如把所有配置项的旧 IP 换成新 IP:

#!/bin/bash config_file="/etc/myapp/config.ini" sed -i 's/192.168.1.100/10.0.0.100/g' "$config_file"

执行前一定要先备份,或者先不带-i输出预览:

sed 's/192.168.1.100/10.0.0.100/g' "$config_file" | head -50

8.3 awk:文本分析与列处理

awk 是一个小型的文本处理语言,最擅长按列处理数据。默认按照空格或制表符切分每一行,$1表示第一列,$NF表示最后一列,NF表示列数,NR表示行号。

先看一个最简单的例子:

# 打印 /etc/passwd 中的用户名和默认 Shell awk -F: '{print $1, $NF}' /etc/passwd

-F:指定冒号为分隔符,因为 passwd 文件是用冒号分隔的。

实际运维中,awk 最常用于分析访问日志。比如统计 nginx 日志中访问量前 10 的 IP:

awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

这里不只是 awk,还串联了sortuniqhead,这就是 Shell 自动化的典型写法:用管道把多个工具组合起来完成一个分析任务。

统计 HTTP 状态码分布:

awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c

计算请求耗时总和和平均值:

awk '{sum += $NF} END {print "请求数", NR, "总耗时", sum, "平均耗时", sum/NR}' /var/log/nginx/access.log

awk 在脚本中还被用来提取进程 PID、磁盘使用率等指标:

#!/bin/bash disk_usage=$(df / | awk 'NR==2{print $5}' | sed 's/%//') echo "根分区使用率: $disk_usage%" if [ "$disk_usage" -gt 85 ]; then echo "磁盘使用率过高,需要清理" fi

8.4 三剑客组合实战:日志分析

把三剑客组合起来,可以完成一条完整的日志分析链路。下面是统计今天的 ERROR 日志按模块分类数量的脚本:

#!/bin/bash today=$(date +%Y-%m-%d) log_file="/var/log/myapp/error_$today.log" if [ ! -f "$log_file" ]; then echo "日志文件不存在: $log_file" exit 1 fi # 提取 ERROR 行,去掉时间戳前缀,按模块分类统计 grep ' ERROR ' "$log_file" \ | sed 's/^\[.*\] \[\(.*\)\] .*/\1/' \ | sort \ | uniq -c \ | sort -rn

这条命令依次完成:过滤错误日志、提取模块名、排序、统计数量、按数量倒序排列。

9. 自动化任务实战

9.1 清理由脚本自动归档后的旧文件

很多服务会产生大量临时文件,需要定期清理超过 7 天的文件:

#!/bin/bash # 清理 /var/log/myapp/ 下超过 7 天的 .log 文件 find /var/log/myapp/ -name "*.log" -mtime +7 -exec ls -lh {} \;

find配合mtime可以找出指定时间之前修改过的文件。先把要删的文件列出来,确认无误后再加-delete

find /var/log/myapp/ -name "*.log" -mtime +7 -delete

不要直接在生产环境执行带-delete的命令。第一次运行先使用ls -lh检查列表,确认范围准确后再清理。

9.2 每周备份数据库

定期备份是一个典型需求。下面脚本将 MySQL 数据库导出为 SQL 文件,并压缩保存:

#!/bin/bash set -euo pipefail BACKUP_DIR="/data/backup/mysql" DB_NAME="myapp" DB_USER="backup_user" DB_PASS="${MYSQL_PASSWORD}" mkdir -p "$BACKUP_DIR" backup_file="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql.gz" mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" | gzip > "$backup_file" echo "备份完成: $backup_file"

这里密码从环境变量读取,而不是硬编码在脚本里,更符合安全规范。

9.3 服务健康检查

结合 curl 检查 HTTP 服务是否正常,失败时重启并发送通知:

#!/bin/bash set -euo pipefail url="http://127.0.0.1:8080/health" service_name="myapp" http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" || echo 000) if [ "$http_code" = "200" ]; then echo "[$(date '+%F %T')] $service_name 状态正常" else echo "[$(date '+%F %T')] $service_name 异常,HTTP 状态码: $http_code" systemctl restart "$service_name" echo "已尝试重启" fi

9.4 定时任务 crontab

脚本写完以后,可以交给 cron 定期执行。先编辑当前用户的 crontab:

crontab -e

添加以下几类配置:

# 每天凌晨 3 点执行备份脚本 0 3 * * * /home/user/shell-practice/backup.sh >> /home/user/shell-practice/backup.log 2>&1 # 每 5 分钟执行健康检查 */5 * * * * /home/user/shell-practice/health_check.sh >> /home/user/shell-practice/health.log 2>&1 # 每周日凌晨 1 点清理日志 0 1 * * 0 find /var/log/myapp/ -name "*.log" -mtime +30 -delete

写 crontab 时注意:脚本里的命令要使用绝对路径,因为 cron 环境与交互式终端不同,PATH 可能不完整。脚本本身最好也加上日志输出和失败重试机制。

9.5 批量服务器巡检

生产环境有多台服务器时,可以写一个带主机列表的巡检脚本:

#!/bin/bash set -euo pipefail hosts_file="/etc/myapp/hosts.txt" log_file="/var/log/myapp/inventory.log" while read -r host; do if [ -z "$host" ]; then continue fi echo "[$(date '+%F %T')] 检查 $host" ping -c 1 -W 2 "$host" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "$host 可达" else echo "$host 不可达" | tee -a "$log_file" fi done < "$hosts_file"

如果要远程执行命令检查磁盘、内存,可以配合 SSH 密钥登录后逐台执行。但务必确认你拥有这些服务器的合法管理权限,避免未授权访问。

9.6 Shell 脚本调用 REST API

Shell 虽然不是写 HTTP 服务端的最佳语言,但作为客户端调用 API 非常方便。下面是一个调用 API 获取数据的示例:

#!/bin/bash set -euo pipefail api_url="https://api.example.com/v1/status" token="${API_TOKEN}" # 发起 GET 请求并保存响应 response=$(curl -s --max-time 10 -H "Authorization: Bearer $token" "$api_url") echo "API 响应: $response" # 检查响应中是否包含 success if echo "$response" | grep -q '"status":"success"'; then echo "任务执行成功" else echo "任务可能失败,需要人工确认" exit 1 fi

发送 POST 请求批量提交数据:

curl -s -X POST "https://api.example.com/v1/tasks" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $token" \ -d '{"task_name": "backup", "target": "web-01"}'

如果接口返回 JSON,并且需要提取字段,可以用jq工具。它专门处理 JSON 数据:

#!/bin/bash response=$(curl -s https://api.example.com/v1/status) task_id=$(echo "$response" | jq -r '.task_id') status=$(echo "$response" | jq -r '.status') echo "任务 ID: $task_id, 状态: $status"

在未安装 jq 的服务器上,可以用yum install jqapt install jq安装,非常轻量。

9.7 批量任务带日志与失败重试

批量任务不能只“无脑跑”,需要记录每个任务的执行状态和失败次数。下面是一个带简单重试机制的脚本:

#!/bin/bash set -euo pipefail task_file="/tmp/tasks.txt" log_file="/var/log/myapp/batch_task.log" max_retry=3 while read -r task; do [ -z "$task" ] && continue retry=0 while [ $retry -lt "$max_retry" ]; do retry=$((retry + 1)) echo "[$(date '+%F %T')] 开始执行任务: $task (第 $retry 次)" if bash -c "$task" >> "$log_file" 2>&1; then echo "[$(date '+%F %T')] 任务成功: $task" break else echo "[$(date '+%F %T')] 任务失败: $task" sleep 2 fi done done < "$task_file" echo "批量任务结束,详细日志见 $log_file"

这个脚本读入任务列表,每条任务最多重试 3 次,并把执行日志追加到日志文件。生产环境如果要控制并发,可以在此基础上加入后台执行与wait

10. 资源占用与性能观察

Shell 脚本本身几乎没有常驻内存消耗,真正影响性能的是脚本里调用的外部命令和循环方式。掌握以下几点,能明显提升脚本执行效率。

第一,减少子进程创建。Shell 中每调用一次外部命令(grep、sed、awk、curl 等)都会创建新进程。如果循环 1000 次,每次执行 3 个外部命令,就会创建 3000 个进程。常见的优化思路是先捕获命令输出,再在变量层面做处理,而不是在循环里反复启动命令。

第二,合理使用管道。管道虽然方便,但每条管道都会产生额外进程。如果只是简单文本处理,可以直接用 awk 完成过滤和统计,避免 “grep + awk + sed” 三个进程串行处理同一份数据。

第三,使用time测算脚本耗时:

time bash batch_task.sh

输出会显示 real、user、sys 三项时间。real 是真实耗时,user 是用户态 CPU 时间,sys 是内核态 CPU 时间。

第四,使用topps观察脚本所在进程的资源占用:

ps -ef | grep batch_task.sh top -p $(pgrep -f batch_task.sh | head -1)

如果脚本在循环中持续占用 CPU,可能是出现了死循环,或外部命令调用链条过长。可以用set -x开启调试模式,观察脚本执行过程,定位卡住的步骤。

第五,控制并发数量。前面提到的后台并发写法能加速任务,但如果一次性启动几十上百个后台进程,系统负载会快速上升。使用简单的并发控制:

#!/bin/bash max_concurrent=5 task_count=0 for ip in 192.168.1.{1..20}; do ( ping -c 1 -W 1 "$ip" > /dev/null 2>&1 && echo "$ip 存活" || echo "$ip 不通" ) & task_count=$((task_count + 1)) if [ $((task_count % max_concurrent)) -eq 0 ]; then wait fi done wait echo "并发巡检完成"

这样做既利用了并行能力,又不会把系统资源打满。

11. 常见问题与排查方法

问题现象可能原因排查方式解决方案
执行脚本报 Permission denied文件没有执行权限ls -l script.shchmod +x script.sh
提示 command not found环境变量 PATH 不完整echo $PATH使用绝对路径,或安装缺失命令
脚本运行时全部正常,但 cron 执行失败cron 环境没有加载用户环境变量在脚本中打印日志,查看 cron 输出脚本开头添加 source 配置文件,或使用绝对路径
/bin/bash^M错误或换行符导致怪问题脚本是 Windows 编辑的,存在 CRLF 换行file script.sh使用sed -i 's/\r$//' script.sh转换
变量比较时报 integer expression expected变量不是数字或为空echo "变量值: $num"检查先做变量判空和类型处理
条件判断一直不生效方括号前后缺少空格检查[]改成[ "$var" = "value" ]
脚本中 rm 或 mv 没有生效变量包含空格,未加引号echo "$file"确认内容变量引用统一加双引号
grep 匹配不到内容但文本确实存在正则表达式错误或文件编码问题cat file | head查看原文调整正则,确认必要编码
awk 输出为空分隔符设置不正确查看原始文本结构根据实际分隔符调整-F
忘了给脚本加执行权限,直接./script.sh运行权限位不足chmod +x script.shbash script.sh临时运行
使用bash script.sh能运行,./script.sh无法运行缺少 shebang 或没有权限head -1 script.sh添加#!/bin/bash并设置权限

Shell 脚本问题排查最重要的不是记住所有语法,而是学会“把执行过程打开看”。在脚本开头加set -x,执行时会把每条命令展开到终端,命令是否正确、变量是否展开都能直接看到。

还需要注意一点:脚本里如果出现rm -rf> file这类破坏性命令,执行前一定要打印确认信息。可以写一段确认逻辑:

#!/bin/bash read -p "确认要删除 $target 目录吗?输入 yes 继续: " confirm if [ "$confirm" != "yes" ]; then echo "已取消" exit 1 fi

这样至少能防止手误造成不可逆后果。

12. 最佳实践与使用建议

第一,脚本开头统一使用set -euo pipefail。这一行能避免大量因为命令失败、变量未定义、管道错误被忽略导致的问题。第一次写脚本的人可能觉得它碍事,但一旦跑过生产任务,就会明白这行保护的价值。

第二,变量引用统一加双引号。"$file"$file的区别,在处理带空格的文件名时会直接决定脚本是否崩溃。这是新手最容易踩的坑。

第三,目录与文件路径分清楚。脚本的工作目录不一定是你执行命令的目录。在脚本开头声明:

BASE_DIR=$(cd "$(dirname "$0")" && pwd) cd "$BASE_DIR"

这样无论从哪里调用脚本,文件路径都能保持稳定。

第四,日志要有时间戳和级别。运维脚本如果没有任何输出,出了问题会非常难查。至少每条关键操作要输出一行带时间戳的信息。

第五,批量任务必须加失败重试和日志。一次跑完就结束的脚本只适合教学,生产环境里网络抖动、资源不足、依赖服务没起来都会导致任务失败。重试机制和日志是底线。

第六,敏感信息不入库。脚本里的密码、Token、私钥路径不要明文写入代码仓库。使用环境变量、配置文件权限控制或专门的密钥管理工具。

第七,合规与授权。批量巡检、远程执行命令、调用第三方接口前,确认你拥有合法授权。不要用 Shell 脚本做未授权扫描、批量爆破、抓取他人数据等操作。测试环境先跑通,生产环境先小范围验证,再加到 cron。

第八,写脚本时就要考虑失败后的影响。不要写那种默认“一切正常”的脚本。每个关键步骤后都要判断退出码,失败时提供明确提示。

13. 从这套实战内容继续往后怎么走

看完这套内容,你已经具备独立编写运维脚本的基础能力。建议先验证的是:把前文的批量重命名、日志分析和服务健康检查脚本在你的 Linux 环境里跑一遍,确认每个环节都能正常输出。

最容易踩的坑集中在三处:一是条件判断两边的空格,二是 Windows 编辑导致的 CRLF 换行,三是脚本执行权限没有设置。这三个问题解决了,大部分脚本都能顺利跑起来。

如果还想继续深入,下一阶段可以按这个顺序扩展:

  • 在现有脚本中引入getopts,支持类似./deploy.sh --env prod --force的参数解析方式;
  • 用 Shell 脚本封装一套自己的工具函数库,把日志、时间格式化、HTTP 请求、文件备份等通用能力抽出来;
  • 结合jq处理 JSON 格式的 API 响应,实现更完整的接口自动化;
  • 把脚本接入 crontab,逐步建立自己的运维自动化任务集;
  • 更进一步,可以关注 Ansible、Python 脚本等更高级的自动化工具,但 Shell 作为底层能力始终不会过时。

Shell 脚本的门槛不在于语法有多难,而在于你能否把零散的 Linux 命令组织成一条清晰、可靠、可维护的执行链。把变量、条件、循环、函数和文本处理这一套流程练熟了,后续再去接触更复杂的自动化工具,底子就会扎实很多。

http://www.cnnetsun.cn/news/4361509.html

相关文章:

  • 面齿轮建模全流程:从Matlab齿面计算到TCA验证
  • 神经元修复:从生物大脑到人工神经网络的工程启示
  • 游戏卡池系统后端设计与实现:概率算法、保底机制与配置实战
  • HarmonyOS 应用开发之多语言国际化:zh_CN/en_US 限定词与 string.json 资源体系详解
  • HoRain云--CSS 属性 选择器
  • 南京矢量数据包全解析:SHP格式处理与坐标系转换实战
  • 生产计划管理有效方法揭秘:如何提升企业运营效率
  • ZYNQ双项目实战:FFT频谱分析与打地鼠游戏开发全流程复盘
  • 用Claude Code Skill实现ASO自动化:从关键词调研到文案生成
  • AlphaGo Zero源码深度解析:从策略网络到自我对弈机制
  • UG871设计文件实战:FPGA高层次综合HLS入门与优化指南
  • 把 ABAP Unit 覆盖率变成发布门禁,生产级自定义 ATC 检查的完整实现
  • 今日老黄历×周易姤卦×12星座运势排行榜
  • 让AI学会“看人下菜碟“:CLEAR解决大模型安全与好用之间的两难
  • 基于ReasonixGUI的DeepSeek Harness客户端:从思路到落地
  • Flask+Vue医院预约挂号系统实战:核心架构与源码解析
  • Excel批量转换数字符号:从基础公式到VBA宏的完整指南
  • 运放电路失真排查指南:从削波、交越失真到自激振荡
  • 超声波焊接塑胶件双工位气密检测:提效原理与产线落地指南
  • AI智能体记忆系统脆弱性分析:从灾难性遗忘到检索失效的工程加固
  • MATLAB实现FDTD二维金属圆柱电磁散射仿真与RCS计算
  • 飞书前端一面面经:45分钟真题与解题思路复盘
  • 大学生宿舍量化交易实战:Python构建加密货币自动交易系统
  • 美团前端一面全复盘:事件循环、React Hooks与大文件上传实战解析
  • LangChain4j+PGVector构建RAG智能客服与工单系统实战
  • H3U与上位机Modbus TCP通信测试全流程实战指南
  • 英雄游戏数据分析岗秋招笔试复盘:SQL、留存率与业务思维全解析
  • 应用安全开发:用户凭证处理与数据加密最佳实践
  • 大模型项目申请翻了5倍,我用这个框架砍掉了80%的无效投入
  • AI客服不自由发挥:硬规则引擎+LLM结构化约束实战方案