尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

从“能跑“到“能打“:我把Shell脚本踩过的坑,攒成了这篇避坑指南

从“能跑“到“能打“:我把Shell脚本踩过的坑,攒成了这篇避坑指南
📅 发布时间:2026/7/2 1:35:51

一、 开头那几行,决定了它是玩具还是工具

很多人的脚本开头是这样的:

#!/bin/bash echo "start..."

这在自己电脑上玩玩没问题,但在生产环境,这就像没系安全带开车。

企业级脚本的标准开头,我一般是这么写的:

#!/usr/bin/env bash # Author: YourName # Date: 2025-05-21 # Desc: 定时清理7天前的日志文件 set -euo pipefail IFS=$'\n\t'

这几行代码的作用,每一个都是血泪教训换来的:

  • #!/usr/bin/env bash:比写死#!/bin/bash更聪明。它会从环境变量$PATH里找bash,无论是CentOS、Ubuntu还是Alpine,都能正确运行,避免了"在我这能跑,在服务器上报错"的尴尬。

  • set -e:这是最重要的安全网。任何命令返回非0(失败),脚本立刻退出。防止错误被忽略,一路滚雪球到最后酿成大祸。

  • set -u:使用了未定义的变量?直接报错退出。还记得我开头的那个惨案吗?就是因为$dir没赋值,脚本执行了rm -rf /。加上这个,如果$dir为空,脚本会在执行删除前就挂掉。

  • set -o pipefail:管道命令的守护神。比如grep "error" log | head,如果grep没找到匹配项(返回1),默认情况下整个管道返回0(因为head成功了)。加上这个参数,管道中任意一环失败,整个管道都算失败。

  • IFS=$'\n\t':防止文件名带空格导致脚本把my file.txt拆成my和file.txt两个参数。


二、 变量与参数:那些看不见的陷阱

1. 永远给变量加双引号

这是一个好习惯,能解决90%的诡异问题。

# 错误示范 file=$1 rm -f /tmp/$file # 正确姿势 file="$1" rm -f "/tmp/$file"

如果不加引号,当$1包含空格时,系统会把它拆成多个参数。更严重的是,如果$1为空,错误示范就变成了rm -f /tmp/,后果不堪设想。

2. 参数校验:不要相信用户的输入

不要假设用户一定会按你的要求传参。

usage() { echo "Usage: $0 <source_dir> <backup_dir>" exit 1 } # 检查参数个数 if [[ $# -ne 2 ]]; then usage fi src="$1" dst="$2" # 检查源目录是否存在 if [[ ! -d "$src" ]]; then echo "Error: Source directory '$src' does not exist." exit 1 fi

养成写usage函数的习惯,并在脚本开头校验所有输入。这是专业和业余的分水岭。

3. 命令替换的现代写法

# 老派写法(不推荐) today=`date +%Y%m%d` # 现代写法(推荐) today=$(date +%Y%m%d) backup_file="backup_${today}.tar.gz"

$()结构清晰,支持嵌套,可读性更强。


三、 流程控制:别让逻辑变成迷宫

1. if判断:用对工具

判断数值和字符串,语法是不同的,混用会导致逻辑错误。

# 数值比较(推荐用双括号) if (( num > 10 )); then echo "大于10" fi # 字符串比较(用双方括号) if [[ "$name" == "admin" ]]; then echo "Welcome admin" fi # 文件判断 if [[ -f "$file" ]]; then echo "是普通文件" elif [[ -d "$dir" ]]; then echo "是目录" fi

记住:(( ))用于算术运算,[[ ]]用于字符串和文件判断。它们比单括号[ ]更强大,也更少出错。

2. for循环:处理文件名带空格的情况

# 潜在危险 for file in *.txt; do process $file done # 安全写法 for file in *.txt; do process "$file" done # 更严谨的写法(防止没有匹配时循环体仍被执行) shopt -s nullglob for file in *.txt; do gzip "$file" done shopt -u nullglob

nullglob选项确保如果没有.txt文件,for循环根本不会执行,避免了把通配符*.txt当作一个字面量参数传给gzip。

3. while读文件:正确处理每一行

# 错误:会按空格拆分一行内容 cat file.txt | while read line; do ... done # 正确:一次读取一整行,包括空格 while IFS= read -r line; do echo "Processing: $line" done < file.txt

IFS=清空分隔符,-r防止反斜杠被解释为转义字符。这是读取文件的"标准答案"。


四、 文本三剑客:从"会用"到"精通"

1. grep:不止是搜索

# 搜索多个关键字 grep -E "error|fail|exception" app.log # 显示匹配行的前后各5行(排查异常上下文神器) grep -B5 -A5 "OutOfMemoryError" app.log # 只输出匹配的部分(正则表达式) grep -oP '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' access.log

2. sed:在线编辑的艺术

# 替换文件内容(Linux) sed -i 's/old/new/g' file.txt # 替换文件内容(macOS兼容写法) sed -i '' 's/old/new/g' file.txt # 替换指定行的内容 sed -i '5s/old/new/' file.txt

⚠️ 警告:使用sed -i前,最好先备份。可以把-i改成-i.bak,这样会在修改前生成一个.bak备份文件。

3. awk:数据统计的瑞士军刀

# 统计Nginx日志中各IP访问次数,取Top 10 awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10 # 更高效的awk原生写法 awk '{count[$1]++} END {for(ip in count) print count[ip], ip}' access.log | sort -rn | head -10 # 计算平均响应时间(假设最后一列是响应时间) awk '{sum+=$NF; cnt++} END {if(cnt>0) printf "Avg: %.2f ms\n", sum/cnt}' access.log

五、 实战进阶:写一个健壮的部署脚本

下面是一个更接近生产环境的脚本示例,融合了前面提到的许多技巧:

#!/usr/bin/env bash set -euo pipefail APP_DIR="/opt/myapp" BACKUP_DIR="/opt/backups/myapp_$(date +%Y%m%d_%H%M%S)" ROLLBACK=0 # 清理函数,无论脚本如何退出都会执行 cleanup() { if [[ $ROLLBACK -eq 1 && -d "$BACKUP_DIR" ]]; then echo "部署失败,开始回滚..." rm -rf "$APP_DIR" mv "$BACKUP_DIR" "$APP_DIR" systemctl restart myapp || true elif [[ -d "$BACKUP_DIR" ]]; then echo "部署成功,备份保留在: $BACKUP_DIR" fi } trap cleanup EXIT echo "开始部署应用..." # 1. 备份当前版本 echo "备份当前版本到 $BACKUP_DIR..." cp -a "$APP_DIR" "$BACKUP_DIR" # 2. 模拟部署新版本(如果这些步骤任何一步失败,脚本会因set -e退出,并触发cleanup回滚) echo "拉取最新代码..." git -C "$APP_DIR" pull origin main || { ROLLBACK=1; exit 1; } echo "安装依赖..." npm --cwd "$APP_DIR" ci --production || { ROLLBACK=1; exit 1; } echo "重启服务..." systemctl restart myapp || { ROLLBACK=1; exit 1; } echo "部署完成!"

这个脚本的精髓在于trap cleanup EXIT和ROLLBACK变量。无论脚本是正常结束还是中途出错,都会执行cleanup函数。如果出错,它会自动回滚到备份版本,实现了部署的"事务性"。


六、 调试与优化:专业选手的工具箱

1. 增强版调试输出

export PS4='+${BASH_SOURCE}:${LINENO}: ' set -x # 你的脚本内容

这样在调试模式下,输出的每一行前面都会显示文件名和行号,方便定位问题。

2. 使用shellcheck进行静态分析

在把脚本放到生产环境前,先用shellcheck检查一下。

# 安装 # yum install ShellCheck 或 apt install shellcheck shellcheck your_script.sh

它会像编译器一样,指出你的脚本中可能存在的语法错误、不良实践和潜在的bug。把它集成到你的CI/CD流程中,能拦截绝大部分低级错误。


七、 总结

Shell脚本看似简单,门槛极低,但写好、写稳、写得安全却是一门学问。它不需要你掌握复杂的算法,但需要你对细节有近乎偏执的关注。

  1. 安全第一:set -euo pipefail和变量双引号是标配。

  2. 防御性编程:永远校验输入,假设一切外部条件都可能出错。

  3. 善用工具:grep/sed/awk的组合拳能解决大部分文本处理问题。

  4. 优雅退出:trap不仅能捕获信号,更能做最后的清理和回滚。

从那个rm -rf的惨痛教训,到现在能写出自动回滚的部署脚本,我走过的弯路希望你能避开。Shell脚本是你管理Linux最锋利的瑞士军刀,善待它,它就会成为你最得力的助手。

你们在写Shell脚本时遇到过哪些离谱的Bug?或者有什么私藏的独门技巧?欢迎在评论区一起交流,让我们共同提升。

相关新闻

  • AI工程化中Harness性能优化实战与调优方法论
  • LangChain 调用 Qwen 与 Ollama 的环境变量笔记
  • 等保测评核心:高危漏洞、高危端口与弱口令的实战防护指南

最新新闻

  • 任务计划程序不显示后边的信息
  • Better BibTeX架构解析:为LaTeX用户打造的企业级文献管理解决方案
  • 如何把报告错误消灭在出稿前?AI报告审核结合IACheck实现前置校验
  • 好用还专业!盘点2026年最强的的降AI率软件
  • 2024年南安多功能太阳能路灯选购指南:3招教你挑对好产品
  • 别再建一个无人问津的知识库:用AI原生平台打造活文档系统

日新闻

  • Python Playwright录制功能:从零到一构建自动化测试脚本
  • 如何用开源工具永久保存你心爱的小说:novel-downloader全攻略
  • In-Context Learning不是教知识,而是模式对齐:从5个示例到100个工业级样本的真相

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号