Shell脚本从零到实战:Linux运维自动化核心技能

发布时间:2026/9/1 4:48:09
Shell脚本从零到实战:Linux运维自动化核心技能 把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推荐用WSLWindows 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 21注意会覆盖原文件是追加写入。生产环境下直接使用要格外谨慎建议先用ls -l确认目标文件路径正确再执行覆盖操作。5.2 grep、sed、awkgrep 负责筛选行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 -10sed -i是原地修改文件属于高风险操作。改动前建议先备份或者先不加-i跑一遍把修改结果打印到终端确认无误。5.3 find、scp、tarfind 用于按条件查找文件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 ops10.0.0.1:/data/backup/ # 远程复制整个目录 scp -r ./scripts/ ops10.0.0.1:/opt/scripts/tar czf里的z表示 gzip 压缩c表示创建归档f指定文件名。date %F会输出当天的日期比如2025-01-01用来拼文件名非常实用。6. Shell脚本核心语法变量、条件、循环、函数常用命令掌握了接着过一遍语法。这些语法是脚本的骨架建议每个都手动敲一遍而不是只看不练。6.1 变量Shell变量不需要声明类型赋值时等号两边不能有空格这是初学者最容易犯的错。#!/bin/bash namenginx port8080 log_path/var/log/${name}/access.log # 使用花括号明确边界 echo 服务名$name echo 端口${port} echo 日志路径$log_path # 命令结果赋值给变量 the_date$(date %Y%m%d) echo 今天的日期是$the_date # 数值运算 a10 b20 total$((a b)) echo 总和是$total使用变量时$name和${name}效果相同。建议在变量后面紧接其他字符时用花括号比如abc_${name}_def否则Shell会认为整个$name_def是变量名。6.2 条件判断if 语句是脚本具备“决策能力”的关键。常见写法如下#!/bin/bash disk_used$(df -h / | awk NR2 {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 21 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.txtfor 循环和 shell 的$?配合可以拿到上一条命令的返回状态0表示成功非0表示失败。这个返回值是脚本做自动判断的依据。6.4 while循环while 循环适合“不断读取”的场景比如逐行读文件、持续监控进程状态。#!/bin/bash count1 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 21 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 21; 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_LISTusers.txt INIT_PASSWORDOps$(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 21; then log_info 用户 ${username} 已存在跳过 else useradd $username echo ${username}:${INIT_PASSWORD} | chpasswd chage -d 0 $username log_info 用户 ${username} 创建成功首次登录需修改密码 fi done $USER_LISTusers.txt的格式是一行一个用户名例如zhangsan lisi wangwuchage -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 返回值的通用示例需要用到curl和jq。jq是处理 JSON 的命令行工具Ubuntu/Debian 下用apt install -y jq安装CentOS 用yum install -y jq。#!/bin/bash set -euo pipefail API_URLhttp://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_URLhttps://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} doneShell 处理 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 21 if [ $? -eq 0 ]; then echo $ip reachable else echo $ip unreachable fi ) done wait echo 所有检测任务执行完成这种写法能明显缩短批处理时间但并发数量过高会导致系统负载飙升生产环境建议控制并发数或者在脚本内做简单限制。11. 定时任务与日志管理脚本写好了还需要让它自动运行这就用到 crontab。crontab 是Linux系统自带的定时任务工具几乎不用额外安装。查看当前用户的定时任务crontab -l编辑定时任务crontab -ecrontab 的格式是五段分、时、日、月、周。下面几个例子覆盖了常见运维场景# 每天凌晨2点执行日志备份脚本 0 2 * * * /opt/scripts/backup_logs.sh /var/log/cron_script.log 21 # 每天早上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.log12. 资源占用与执行性能观察Shell脚本本身不占多少资源但脚本里启动的外部命令会消耗CPU和内存。运维写脚本时要注意观察执行时间、进程数量和系统负载不能写出“跑一次拖垮服务器”的脚本。查看脚本执行时间最直接的办法是timetime bash deploy_nginx.sh输出里会包含 real、user、sys 三个时间。其中 user 是用户态CPU时间sys 是内核态CPU时间。如果脚本执行时间异常长重点排查是否有重复调用耗时命令、是否使用了死循环、日志文件是否过大。用ps和top可以观察到脚本执行过程中启动的子进程# 假设脚本在后台执行 bash backup_logs.sh ps -ef | grep backup_logs批量处理大量文件时脚本效率通常会败给一个细节在循环里反复启动外部命令。比如要给一万个小文件修改权限直接find /data -type f -exec chmod 644 {} \;比在 Shell 循环里一条条执行chmod快得多。能用系统自带工具完成的批量操作尽量不用 Shell 循环去拼这是性能优化的第一原则。内存占用方面Shell 脚本自身几乎可以忽略但如果脚本里加载了巨大的配置文件、使用cat读取超大文本然后循环处理内存和CPU占用就会上来。处理超大文件时优先使用grep、awk、sed这类流式处理工具而不是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.sh或cat -A test.sh查看末尾字符使用dos2unix test.sh或 vim 中执行:set ffunix后保存报错command not found命令不在 PATH 中或脚本没加解释器echo $PATH检查head -1 test.sh检查首行脚本首行写#!/bin/bash命令使用绝对路径变量赋值后输出为空等号两边有空格例如name tomcat -A test.sh查看符号写成nametom等号两边不要空格if 判断报[: too many arguments变量没加双引号bash -x 跟踪执行把变量处理为[ $var xxx ]脚本中途失败但没提示没开 set -e观察脚本执行到哪一步开头加set -euo pipefail用bash -x追踪crontab 任务没执行环境变量不同、脚本无执行权限、路径写错检查/var/log/cron或journalctl -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 fi14. 最佳实践与使用建议技术会了工程习惯也要到位。这里给出一套实际工作中沉淀下来的Shell脚本规范建议从第一天写脚本就养成。第一脚本文件的头部统一包含功能说明、作者、创建日期、版本号。这不是形式主义是排障时的核心线索。等到几个月后回看脚本能直接知道这套逻辑当初为什么这么写。#!/bin/bash # # 功能每日业务数据备份 # 作者ops # 创建日期2025-01-01 # 版本v1.0 # 变更记录2025-01-10 增加远程备份 # set -euo pipefail第二变量命名要有意义。ip、port、log_path这种命名比a、b、c可读性高出几个量级。函数名用动词开头比如check_disk、backup_data、send_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_LOGfailed_$(date %F).log for ip in $(cat server_list.txt); do if ! ssh -o ConnectTimeout3 ops$ip uptime /dev/null 21; 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去跟踪自己写的脚本把每一步执行过程看清楚。第四抽时间学一点PythonShell处理文本和调用外部命令是强项但复杂逻辑和数据处理用Python更稳两者配合是运维日常最常用的组合。最容易踩的坑其实不是语法而是“不问路径乱执行”和“不改验证直接生产”。每写一个脚本先在测试环境跑三遍第一遍看语法第二遍看输出第三遍看边界情况。等这套习惯养成了Shell脚本就不再是背命令的负担而是你处理服务器问题时的第一反应工具。