Shell脚本从零到实战:Linux运维自动化核心技能
2026/9/1 4:48:06 网站建设 项目流程

把Linux运维当跳板转行IT的人不少,但真正进了公司才会发现:日常工作中最频繁的“查日志、备份数据、批量改配置、盯服务状态”,靠的全是命令行和脚本,而不是鼠标点界面。Shell脚本就是运维手里的那台“自动化机床”,别人一条一条敲命令,你把命令组织成脚本一次跑完,效率差距立刻就出来了。这篇文章不绕弯子,直接带你从零开始写Shell脚本:先讲清楚它到底解决什么问题,再给出一套能直接上手的语法和实战案例,最后补齐调试排错和规范建议。适合准备转行运维的初学者,也适合已经干运维但脚本还停留在“复制粘贴改IP”阶段的同学。

这次我们看的是一个纯粹的技能路线,不涉及显存、显卡、API服务这些AI部署概念,但它的逻辑和本地部署工具是一致的:先确认环境,再跑通最小例子,再逐步做成批量任务和自动化服务。Shell脚本的入门门槛很低,只要有一台Linux机器或者Windows上的WSL、虚拟机,就能开始练。下面直接进正文,从核心能力、环境准备开始,一直到写出一套可复用的运维实战脚本。

1. Shell脚本核心能力速览

能力项说明
项目类型Linux运维基础技能,命令行脚本编写
主要用途自动化执行命令、批量文件处理、日志分析、服务部署、定时任务、监控报警
核心环境Linux发行版(CentOS / Ubuntu / Debian / 统信UOS 等均可),或 Windows 下的 WSL、虚拟机
硬件要求1核1G内存起步即可,普通办公电脑完全够用,无GPU需求
入门难度低,掌握 Linux 基础命令后即可开始
扩展能力可调用系统命令、Python脚本、curl请求HTTP接口、操作数据库客户端
批量任务天然支持 for / while 循环、xargs、并发后台执行
排错方式bash -x 跟踪执行、set -e 遇错退出、shellcheck 静态检查

从能力表格能看明白:Shell脚本不是某个具体的软件工具,而是一种组织命令的方式。它把Linux系统里零零散散的指令、管道、重定向和条件判断组合成一个可重复执行的文件,运维同学通过它把“手动操作”变成“一键执行”。

2. 适用场景与使用边界

Shell脚本最适合以下几类场景:首先是重复性操作,比如每天上班先检查所有服务器的磁盘使用率、服务进程状态、nginx错误日志,这类工作写成脚本一次跑完,比手动敲几十条命令靠谱得多。其次是批量处理,比如给200台机器批量推送配置、批量创建用户、批量修改文件权限,用循环加远程执行命令十几行就能解决。第三是定时任务,配合 crontab 可以做到每天凌晨自动备份数据库、自动清理过期日志、自动生成日报数据。

同时也要说清楚边界。Shell脚本不适合做复杂的数值计算和数据结构处理,真要分析海量日志、做复杂统计,交给Python更合适。Shell也不适合编写太长太复杂的业务系统,脚本超过几百行之后,调试成本和隐患都会上升,这时候应该用更规范的语言去实现。还有一个容易被忽略的点:Shell脚本里如果内联了SQL、Python代码、curl请求长JSON,转义和引号处理会非常痛苦,可读性也差,建议直接调用外部脚本文件。

合规上需要多说一句:Shell脚本拥有系统命令执行权限,凡是涉及生产环境操作、批量修改用户、修改权限、删除文件、远程执行命令的脚本,必须先在小范围测试环境验证,明确操作目的并获得授权后再上生产。不要随意执行来源不明的脚本,尤其是curl xxx | bash这类方式,这是安全大忌。

3. 环境准备与前置条件

写Shell脚本几乎不需要安装额外软件,系统自带。你需要确认以下环境。

3.1 Linux系统环境

准备一台Linux机器即可。如果本机是Windows,推荐用WSL(Windows Subsystem for Linux)或者VMware装一台虚拟机;如果已经有云服务器,直接用云服务器练习更接近生产环境。国内办公环境下,统信UOS、麒麟等国产系统也支持标准Shell脚本,语法与CentOS/Ubuntu一致。

进入终端后先确认当前使用的Shell解释器:

echo $SHELL # 常见输出:/bin/bash

绝大多数系统默认Shell是bash。虽然sh、zsh、fish也可以跑脚本,但运维场景里bash是绝对主流。本文所有示例均基于bash。

3.2 编辑器准备

本机推荐使用 VSCode 加 Remote-SSH 插件,或者直接用系统自带 vim。对于初学者,VSCode 的语法高亮和格式化更友好,还能安装 shellcheck 插件做静态检查。服务器上则使用 vim 较多,至少需要掌握i进入编辑、Esc退出编辑、:wq保存退出、:q!强制退出这几个基本操作。

3.3 基本命令检查

写脚本之前,建议先确认以下基础命令可用,它们是脚本里的常用“零件”:

# 查看磁盘占用 df -h # 查看内存占用 free -h # 查看进程 ps aux | head # 查找文件 find /var/log -name "*.log" | head # 文本处理 grep "error" /var/log/messages | head

如果这些命令能正常输出,环境就算准备好了。缺命令时用系统自带包管理器安装,比如 CentOS/RHEL 系用yum install -y 包名,Ubuntu/Debian 系用apt install -y 包名

4. 第一个Shell脚本:从Hello World到带参数执行

环境没问题,直接动手写第一个脚本。进入一个练习目录,创建脚本文件。

mkdir -p ~/shell-practice && cd ~/shell-practice vim hello.sh

写入以下内容:

#!/bin/bash # 这是注释:打印一段文字 echo "Hello, Linux Ops!" echo "当前登录用户是:$USER" echo "当前目录是:$(pwd)"

第一行#!/bin/bash叫做shebang,声明这个脚本用哪个解释器来执行。后面以#开头的是注释,$USER是系统自带的环境变量,$(pwd)表示执行 pwd 命令并把结果拼接到字符串里。

执行脚本有三种方式:

# 方式一:直接用 bash 解释器执行,不要求执行权限 bash hello.sh # 方式二:先赋执行权限再执行 chmod +x hello.sh ./hello.sh # 方式三:用 sh 执行(等价于 bash 的兼容模式) sh hello.sh

初学者最容易踩的坑是直接执行./hello.sh时忘记加chmod,系统会报Permission denied。此时要么加执行权限,要么改用bash hello.sh。在编写阶段,我建议都用bash hello.sh快速验证,不用反复处理权限问题;等脚本稳定了再统一chmod

接下来让脚本接收外部参数。运维脚本经常需要传入IP、文件路径、端口等变量,这就要用到位置参数。

修改脚本为:

#!/bin/bash echo "脚本名:$0" echo "第一个参数:$1" echo "第二个参数:$2" echo "参数总数:$#" echo "所有参数:$@"

执行:

bash hello.sh 10.0.0.1 8080

输出如下:

脚本名:hello.sh 第一个参数:10.0.0.1 第二个参数:8080 参数总数:2 所有参数:10.0.0.1 8080

$0代表脚本自身,$1$2代表第一个、第二个参数,$#是参数个数,$@是所有参数。这套机制是后面写批量脚本的基础。

5. 运维常用命令组合:管道、重定向与三剑客

Shell脚本的本质是“命令编织”。想写出一套有实际价值的脚本,必须熟练掌握常用Linux命令的基本用法。下面按运维日常工作最常见的使用场景过一遍。

5.1 管道与重定向

管道|把上一个命令的输出传给下一个命令处理,重定向>>>把输出写入文件。

# 查看当前目录下文件大小并按大小排序 ls -lh | sort -k5 -h # 统计日志文件总行数 wc -l < app.log # 把错误日志追加到文件末尾 tail -n 100 /var/log/nginx/error.log >> nginx_error.txt # 将标准输出和标准错误分别写入文件 find / -name "*.conf" > conf_list.txt 2> error.txt # 丢弃不需要的输出 rm -rf /tmp/cache_dir > /dev/null 2>&1

注意>会覆盖原文件,>>是追加写入。生产环境下直接使用>要格外谨慎,建议先用ls -l确认目标文件路径正确,再执行覆盖操作。

5.2 grep、sed、awk

grep 负责筛选行,sed 负责替换和处理行,awk 负责按列拆分统计。三者组合起来能解决日志分析和配置修改的绝大部分需求。

# grep:提取包含 error 的行并显示行号 grep -n "error" app.log # grep:排除包含 DEBUG 的行 grep -v "DEBUG" app.log # sed:把配置文件里的 80 端口替换为 8080,并备份原文件 sed -i.bak 's/listen 80/listen 8080/' nginx.conf # awk:打印日志第一列(通常是时间或IP)并按第二列统计次数 awk '{print $1}' access.log | sort | uniq -c | sort -rn # 组合:统计每个IP的访问次数,取前10 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

sed -i是原地修改文件,属于高风险操作。改动前建议先备份,或者先不加-i跑一遍把修改结果打印到终端确认无误。

5.3 find、scp、tar

find 用于按条件查找文件,scp 用于跨机器复制文件,tar 用于打包解压。运维迁移文件、归档日志基本离不开这三个。

# 查找 /var/log 下7天前的 .log 文件 find /var/log -name "*.log" -mtime +7 # 查找大于500M的文件 find /data -type f -size +500M # 本地打包并压缩目录 tar czf backup_$(date +%F).tar.gz /data/app # 远程复制文件到服务器 scp backup_2025-01-01.tar.gz ops@10.0.0.1:/data/backup/ # 远程复制整个目录 scp -r ./scripts/ ops@10.0.0.1:/opt/scripts/

tar czf里的z表示 gzip 压缩,c表示创建归档,f指定文件名。date +%F会输出当天的日期,比如2025-01-01,用来拼文件名非常实用。

6. Shell脚本核心语法:变量、条件、循环、函数

常用命令掌握了,接着过一遍语法。这些语法是脚本的骨架,建议每个都手动敲一遍,而不是只看不练。

6.1 变量

Shell变量不需要声明类型,赋值时等号两边不能有空格,这是初学者最容易犯的错。

#!/bin/bash name="nginx" port=8080 log_path="/var/log/${name}/access.log" # 使用花括号明确边界 echo "服务名:$name" echo "端口:${port}" echo "日志路径:$log_path" # 命令结果赋值给变量 the_date=$(date +%Y%m%d) echo "今天的日期是:$the_date" # 数值运算 a=10 b=20 total=$((a + b)) echo "总和是:$total"

使用变量时,$name${name}效果相同。建议在变量后面紧接其他字符时用花括号,比如abc_${name}_def,否则Shell会认为整个$name_def是变量名。

6.2 条件判断

if 语句是脚本具备“决策能力”的关键。常见写法如下:

#!/bin/bash disk_used=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//') if [ "$disk_used" -gt 80 ]; then echo "磁盘使用率告警:当前 ${disk_used}%" else echo "磁盘使用率正常:当前 ${disk_used}%" fi

条件判断里务必给变量加双引号,比如"$disk_used"。否则当变量为空时,[ $disk_used -gt 80 ]会报语法错误。

除了数值比较,还经常判断文件是否存在:

#!/bin/bash config_file="/etc/nginx/nginx.conf" if [ -f "$config_file" ]; then echo "配置文件存在,开始校验..." nginx -t else echo "配置文件不存在:$config_file" exit 1 fi

常用文件判断选项包括:-f是否普通文件、-d是否目录、-e路径是否存在、-r是否可读、-w是否可写、-x是否可执行。字符串判断用[ "$a" = "$b" ],空值用[ -z "$var" ]

6.3 for循环

for 循环是批量任务的核心工具。运维场景里经常要遍历一批机器、遍历目录里的文件、遍历一组端口。

#!/bin/bash # 遍历固定列表 for ip in 10.0.0.1 10.0.0.2 10.0.0.3; do echo "准备检查:$ip" ping -c 1 -W 1 "$ip" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "$ip 网络可达" else echo "$ip 网络不可达" fi done

更常见的写法是从文件读取批量列表:

#!/bin/bash # server_list.txt 每行一个服务器IP while IFS= read -r ip; do [ -z "$ip" ] && continue echo "处理服务器:$ip" done < server_list.txt

for 循环和 shell 的$?配合可以拿到上一条命令的返回状态,0表示成功,非0表示失败。这个返回值是脚本做自动判断的依据。

6.4 while循环

while 循环适合“不断读取”的场景,比如逐行读文件、持续监控进程状态。

#!/bin/bash count=1 while [ $count -le 5 ]; do echo "第 ${count} 次循环" count=$((count + 1)) done

实际运维中更常用的是这个模式:监控进程是否存在,不存在则启动并发送告警。

#!/bin/bash while true; do if ! pgrep -f "java -jar app.jar" > /dev/null; then echo "应用进程不存在,正在重启..." nohup java -jar app.jar > app.log 2>&1 & fi sleep 30 done

这种脚本通常用nohup bash monitor.sh &放到后台运行,但要注意:生产环境写这类自愈脚本要设计好退出条件,否则进程崩溃后无限重启会造成雪上加霜。

6.5 函数

脚本长了以后,把重复逻辑提取成函数能让代码可读性大幅提升。

#!/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" } check_port() { local port=$1 if ss -lntp | grep -q ":${port} "; then log_info "端口 ${port} 已被占用" return 1 else log_info "端口 ${port} 空闲" return 0 fi } log_info "开始检查端口" check_port 8080 check_port 3306 log_error "端口冲突风险已记录"

函数里可以用return返回状态码,调用方通过$?获取。local声明的变量只在函数内部有效,避免污染全局变量。

7. 实战一:一键部署Nginx脚本

前面语法都过了一遍,现在串起来写一个完整的实战脚本。下面是一套“一键部署Nginx”流程,包含环境检查、安装、配置备份、启动校验四个环节。不同Linux发行版包管理器不一样,这里以 Ubuntu/Debian 的 apt 为例,CentOS 系把安装命令替换成yum install -y nginx即可。

#!/bin/bash set -euo pipefail NGINX_CONF="/etc/nginx/nginx.conf" BACKUP_DIR="/data/backup/nginx" log_info() { echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $1" } # 1. 检查是否root执行 if [ "$(id -u)" -ne 0 ]; then echo "[ERROR] 请使用 root 用户或 sudo 执行此脚本" exit 1 fi # 2. 检查Nginx是否已安装 if command -v nginx > /dev/null 2>&1; then log_info "Nginx 已安装,跳过安装步骤" else log_info "Nginx 未安装,开始安装..." apt update apt install -y nginx fi # 3. 备份现有配置 if [ -f "$NGINX_CONF" ]; then mkdir -p "$BACKUP_DIR" cp "$NGINX_CONF" "${BACKUP_DIR}/nginx.conf.$(date +%Y%m%d_%H%M%S)" log_info "配置已备份到 ${BACKUP_DIR}" fi # 4. 启动并校验 systemctl enable nginx systemctl restart nginx nginx -t log_info "Nginx 部署完成,当前状态:" systemctl status nginx --no-pager | head -5

这个脚本里有一个很明显的变化:开头加了set -euo pipefail。这行是给脚本上“保险”的:-e表示脚本中任何命令返回非0状态立刻退出,-u表示使用未定义变量时报错,pipefail表示管道命令中只要有一个环节失败整体就算失败。写生产脚本强烈建议加上。

执行脚本前先检查语法:

bash -n deploy_nginx.sh chmod +x deploy_nginx.sh sudo ./deploy_nginx.sh

第一次执行如果环境没装过Nginx,日志会输出安装过程;再次执行会跳过安装直接进入备份和启动流程。这就是“幂等脚本”的思路:同一套脚本跑多次,结果一致且安全。

8. 实战二:批量创建用户与权限初始化脚本

转行运维后,“上线一批新服务器需要批量创建用户”几乎是必遇场景。下面的脚本从文本文件读取一组用户名,批量创建用户、设置初始密码、创建家目录,并强制首次登录修改密码。注意:生产环境创建用户和设置密码必须由管理员在授权范围内执行,不能有“随便建用户”的想法。

#!/bin/bash set -euo pipefail USER_LIST="users.txt" INIT_PASSWORD="Ops@$(date +%s)" log_info() { echo "$(date '+%Y-%m-%d %H:%M:%S') [INFO] $1" } if [ ! -f "$USER_LIST" ]; then echo "[ERROR] 文件 ${USER_LIST} 不存在" exit 1 fi while IFS= read -r username; do [ -z "$username" ] && continue if id "$username" > /dev/null 2>&1; then log_info "用户 ${username} 已存在,跳过" else useradd "$username" echo "${username}:${INIT_PASSWORD}" | chpasswd chage -d 0 "$username" log_info "用户 ${username} 创建成功,首次登录需修改密码" fi done < "$USER_LIST"

users.txt的格式是一行一个用户名,例如:

zhangsan lisi wangwu

chage -d 0的作用是强制该用户在下次登录时修改密码。脚本里生成的初始密码是Ops@加当前时间戳,每次执行都会不一样,避免所有用户用同一个固定弱密码。

9. 实战三:日志筛选与统计脚本

日志分析是运维日常里最消耗精力的工作。下面这个脚本统计 Nginx 访问日志里的状态码分布、Top 10 访问IP、404 错误出现频次,输出到独立文件,方便每天早上一眼看到昨天的情况。

#!/bin/bash set -euo pipefail LOG_FILE="/var/log/nginx/access.log" OUTPUT_DIR="/data/report" DATE_STR=$(date -d yesterday +%Y%m%d) OUTPUT_FILE="${OUTPUT_DIR}/nginx_report_${DATE_STR}.txt" mkdir -p "$OUTPUT_DIR" if [ ! -f "$LOG_FILE" ]; then echo "[ERROR] 日志文件不存在:$LOG_FILE" exit 1 fi { echo "===== 昨日访问统计:$(date -d yesterday +%F) =====" echo "总访问次数:$(wc -l < "$LOG_FILE")" echo "" echo "===== HTTP 状态码分布 =====" awk '{print $9}' "$LOG_FILE" | sort | uniq -c | sort -rn echo "" echo "===== Top 10 访问IP =====" awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10 echo "" echo "===== 404 错误请求 TOP 20 =====" awk '$9 == 404 {print $1, $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -20 } > "$OUTPUT_FILE" echo "报告已生成:$OUTPUT_FILE"

这段用了一个{}块把多次输出统一重定向到同一个报告文件。脚本里的awk '{print $9}'是根据默认日志格式取HTTP状态码,如果你的日志格式不是默认的 combined 格式,列号需要调整。

执行脚本后,可以用tail -n 30查看报告内容。这个脚本同样适合放进 crontab 每天早上自动跑。

10. 接口与批量任务:Shell脚本如何调用API

很多初学者以为Shell脚本只能处理本地文件,实际上它完全可以充当一个轻量级的接口调用客户端。运维自动化里经常用 Shell 脚本请求内部监控平台、告警机器人、发布系统的 REST API。

下面是一个调用 HTTP 接口并解析 JSON 返回值的通用示例,需要用到curljqjq是处理 JSON 的命令行工具,Ubuntu/Debian 下用apt install -y jq安装,CentOS 用yum install -y jq

#!/bin/bash set -euo pipefail API_URL="http://127.0.0.1:8080/api/health" response=$(curl -s -m 5 "$API_URL") # 用 jq 提取 .status 字段 status=$(echo "$response" | jq -r '.status') if [ "$status" = "ok" ]; then echo "接口正常,实时数据:" echo "$response" | jq . else echo "接口异常,返回内容:" echo "$response" exit 1 fi

实际场景中很可能要 POST JSON 数据。下面是一个发送告警消息到群机器人的示例骨架:

#!/bin/bash set -euo pipefail WEBHOOK_URL="https://example.com/webhook/send" send_alert() { local message=$1 local payload payload=$(cat <<EOF { "msg_type": "text", "content": "${message}" } EOF ) curl -s -X POST "$WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "$payload" \ -m 10 } send_alert "磁盘使用率超过阈值,请及时处理"

结合 for 循环做批量接口测试也非常方便:

#!/bin/bash for port in 8080 8081 8082; do code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:${port}/health") echo "端口 ${port} 健康检查状态码:${code}" done

Shell 处理 API 有两个注意点:第一,复杂 JSON 建议用 jq 构造,而不是手工字符串拼接,避免引号转义问题;第二,curl 请求一定要加-m超时参数,否则接口卡住时脚本会一直挂在那里。

批量任务方面,Shell 还可以做并发加速。用&把任务放到后台,然后用wait等待全部结束:

#!/bin/bash # 并发执行多个耗时任务 for ip in 10.0.0.1 10.0.0.2 10.0.0.3; do ( ping -c 2 -W 2 "$ip" > /dev/null 2>&1 if [ $? -eq 0 ]; then echo "$ip reachable" else echo "$ip unreachable" fi ) & done wait echo "所有检测任务执行完成"

这种写法能明显缩短批处理时间,但并发数量过高会导致系统负载飙升,生产环境建议控制并发数,或者在脚本内做简单限制。

11. 定时任务与日志管理

脚本写好了,还需要让它自动运行,这就用到 crontab。crontab 是Linux系统自带的定时任务工具,几乎不用额外安装。

查看当前用户的定时任务:

crontab -l

编辑定时任务:

crontab -e

crontab 的格式是五段:分、时、日、月、周。下面几个例子覆盖了常见运维场景:

# 每天凌晨2点执行日志备份脚本 0 2 * * * /opt/scripts/backup_logs.sh >> /var/log/cron_script.log 2>&1 # 每天早上8点生成nginx访问报告 0 8 * * * /opt/scripts/nginx_report.sh # 每5分钟检查一次关键进程 */5 * * * * /opt/scripts/check_process.sh # 每周日凌晨3点清理30天前的临时文件 0 3 * * 0 find /tmp -type f -mtime +30 -delete

关于定时任务有几个重要细节。第一,crontab 的环境变量和登录Shell不完全一样,脚本里如果需要特殊 PATH,建议在脚本开头显式导出,或者用绝对路径调用命令。第二,脚本的输出默认不会显示,要排查问题必须把 stdout 和 stderr 重定向到日志文件。第三,分钟、小时等字段支持*/5这类步长写法,但不要写成过于复杂的表达式,防止自己都看不懂。

写日志轮转也可以用 Shell 脚本完成。下面是一个日志归档脚本的骨架,按日期切割昨天的日志并压缩:

#!/bin/bash set -euo pipefail LOG_DIR="/var/log/myapp" YESTERDAY=$(date -d yesterday +%Y%m%d) cd "$LOG_DIR" for log in app.log error.log; do if [ -f "$log" ]; then mv "$log" "${log}.${YESTERDAY}" gzip "${log}.${YESTERDAY}" fi done # 重新创建空日志文件 touch app.log error.log

12. 资源占用与执行性能观察

Shell脚本本身不占多少资源,但脚本里启动的外部命令会消耗CPU和内存。运维写脚本时要注意观察执行时间、进程数量和系统负载,不能写出“跑一次拖垮服务器”的脚本。

查看脚本执行时间最直接的办法是time

time bash deploy_nginx.sh

输出里会包含 real、user、sys 三个时间。其中 user 是用户态CPU时间,sys 是内核态CPU时间。如果脚本执行时间异常长,重点排查是否有重复调用耗时命令、是否使用了死循环、日志文件是否过大。

pstop可以观察到脚本执行过程中启动的子进程:

# 假设脚本在后台执行 bash backup_logs.sh & ps -ef | grep backup_logs

批量处理大量文件时,脚本效率通常会败给一个细节:在循环里反复启动外部命令。比如要给一万个小文件修改权限,直接find /data -type f -exec chmod 644 {} \;比在 Shell 循环里一条条执行chmod快得多。能用系统自带工具完成的批量操作,尽量不用 Shell 循环去拼,这是性能优化的第一原则。

内存占用方面,Shell 脚本自身几乎可以忽略,但如果脚本里加载了巨大的配置文件、使用cat读取超大文本然后循环处理,内存和CPU占用就会上来。处理超大文件时优先使用grepawksed这类流式处理工具,而不是cat后配合循环逐行遍历。

磁盘占用也要留意。脚本内大量拼接字符串生成中间文件时,要定时清理;发送大量日志到远程服务器时要考虑带宽占用。总之记住一句话:脚本是自动化工具,不是资源黑洞,跑完要检查系统负载。

13. 常见问题与排查方法

Shell脚本报错是每天都会遇到的事。下面整理一份高频排查表,遇到问题可以直接对照。

问题现象可能原因排查方式解决方案
执行./test.sh报 Permission denied脚本没有执行权限ls -l test.sh查看权限chmod +x test.sh或改用bash test.sh
报错bad interpreter: /bin/bash^M脚本文件是 Windows 换行符 CRLFfile test.shcat -A test.sh查看末尾字符使用dos2unix test.sh,或 vim 中执行:set ff=unix后保存
报错command not found命令不在 PATH 中,或脚本没加解释器echo $PATH检查;head -1 test.sh检查首行脚本首行写#!/bin/bash;命令使用绝对路径
变量赋值后输出为空等号两边有空格,例如name = "tom"cat -A test.sh查看符号写成name="tom",等号两边不要空格
if 判断报[: too many arguments变量没加双引号bash -x 跟踪执行把变量处理为[ "$var" = "xxx" ]
脚本中途失败但没提示没开 set -e观察脚本执行到哪一步开头加set -euo pipefail,用bash -x追踪
crontab 任务没执行环境变量不同、脚本无执行权限、路径写错检查/var/log/cronjournalctl -u crond脚本内写绝对路径,输出重定向到日志文件
curl 请求接口卡死没设置超时时间手动执行 curl 观察-m 10指定超时
for 循环处理大量文件很慢循环里频繁启动外部命令time bash script.sh观察耗时换成 find + xargs 或 awk 一次性处理

这里再重点讲一个高频坑:查看脚本字符编码和换行符。Windows 下用记事本编辑过的脚本上传到Linux,经常会带上\r\n换行符,Linux 只认\n。此时执行脚本会直接报bad interpreter。推荐先用file命令确认格式:

file test.sh # 正常输出:ASCII text # 异常输出:ASCII text, with CRLF line terminators

修复方式:

sed -i 's/\r$//' test.sh # 或者 dos2unix test.sh

另一个高频问题是脚本里使用sudo和普通用户权限混淆。如果脚本必须用 root 执行,建议开头做权限检查:

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

14. 最佳实践与使用建议

技术会了,工程习惯也要到位。这里给出一套实际工作中沉淀下来的Shell脚本规范,建议从第一天写脚本就养成。

第一,脚本文件的头部统一包含功能说明、作者、创建日期、版本号。这不是形式主义,是排障时的核心线索。等到几个月后回看脚本,能直接知道这套逻辑当初为什么这么写。

#!/bin/bash # ============================================= # 功能:每日业务数据备份 # 作者:ops # 创建日期:2025-01-01 # 版本:v1.0 # 变更记录:2025-01-10 增加远程备份 # ============================================= set -euo pipefail

第二,变量命名要有意义。ipportlog_path这种命名比abc可读性高出几个量级。函数名用动词开头,比如check_diskbackup_datasend_alert

第三,脚本里涉及删除操作必须加保护。例如删除临时目录前先判断路径是否为空,再判断是否为项目目录,防止一条错误的rm -rf酿成事故。可以加入“确认提示”或“安全模式”:

#!/bin/bash rm_confirm() { local target=$1 if [ -z "$target" ] || [ "$target" = "/" ]; then echo "[ERROR] 非法删除路径" return 1 fi read -r -p "确认删除 ${target} ? [y/N] " answer if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then rm -rf "$target" else echo "已取消删除" fi } rm_confirm /data/temp_cache

第四,日志和失败重试要配套。批量任务里,单条失败不能直接中断整个流程,应该把失败的IP或文件名记录下来,最后统一生成报告。简单做法是把失败信息追加到failed_xxx.txt

#!/bin/bash FAILED_LOG="failed_$(date +%F).log" for ip in $(cat server_list.txt); do if ! ssh -o ConnectTimeout=3 ops@"$ip" "uptime" > /dev/null 2>&1; then echo "$ip" >> "$FAILED_LOG" fi done echo "执行完成,失败列表见 $FAILED_LOG"

第五,涉及生产环境的脚本先在小范围测试。可以先用一台测试服务器,或者用--dry-run模式只打印即将执行的命令,不实际执行。上线前用bash -n做语法检查,用 shellcheck 做静态检查:

yum install -y shellcheck # 或 apt install -y shellcheck shellcheck deploy_nginx.sh

第六,脚本目录要规范。建议把脚本统一放在/opt/scripts,日志输出放在/data/logs,临时文件放在/tmp下并定期清理。模型文件、备份包的路径不要在脚本里写死,通过变量或配置项管理。

第七,涉及用户、权限、远程执行的脚本必须确认授权。批量创建用户、批量重置密码、远程执行命令都属于敏感操作,先走审批流程,再在测试环境验证,最后才上生产。不执行来源不明的脚本。

第八,写脚本时预留“环境变量覆盖”能力。比如数据库密码、服务器IP、端口这些信息,不要在脚本里硬编码,用变量初始化并允许外部覆盖:

DB_HOST=${DB_HOST:-"127.0.0.1"} DB_PORT=${DB_PORT:-"3306"} DB_USER=${DB_USER:-"ops"}

这样同一套脚本可以通过不同环境变量适配开发、测试、生产环境,不需要改代码。

15. 总结与下一步

Shell脚本是Linux运维的入门第一课,也是涨薪进阶的底层能力。看完这篇文章,你应该已经掌握了一条完整的学习路径:先准备Linux环境并确认bash解释器,然后写好第一个hello world脚本,接着把变量、if判断、for/while循环、函数这些语法跑熟,再用“一键部署Nginx、批量创建用户、日志统计”三个实战把知识串起来,最后补上crontab定时执行、curl调用接口、shellcheck检查和故障排查能力。这套流程下来,你已经有能力独立处理生产环境里的大部分重复性运维工作。

接下来建议按这个顺序继续练:第一,把文章里的三个实战脚本自己敲一遍,改成你本机的路径和包管理器,确保能跑通。第二,写一个自己的综合脚本,比如每天自动备份数据库、压缩日志并推送到远程服务器,至少包含函数、循环、日志重定向和失败检测。第三,用bash -x去跟踪自己写的脚本,把每一步执行过程看清楚。第四,抽时间学一点Python,Shell处理文本和调用外部命令是强项,但复杂逻辑和数据处理用Python更稳,两者配合是运维日常最常用的组合。

最容易踩的坑其实不是语法,而是“不问路径乱执行”和“不改验证直接生产”。每写一个脚本,先在测试环境跑三遍:第一遍看语法,第二遍看输出,第三遍看边界情况。等这套习惯养成了,Shell脚本就不再是背命令的负担,而是你处理服务器问题时的第一反应工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询