2026/1/16 5:12:20
网站建设
项目流程
在线视频网站建设,农村电商怎么做,谷歌google 官网下载,怎样注册自己的微信小程序写了十年运维脚本#xff0c;最深的体会是#xff1a;Bash不难#xff0c;难的是写出不坑人的脚本。
见过太多能跑但一改就崩的脚本#xff0c;也踩过不少自己挖的坑。这篇把我积累的经验整理出来#xff0c;都是血泪教训。为什么还要学Bash
有人说现在都用Py…写了十年运维脚本最深的体会是Bash不难难的是写出不坑人的脚本。见过太多能跑但一改就崩的脚本也踩过不少自己挖的坑。这篇把我积累的经验整理出来都是血泪教训。为什么还要学Bash有人说现在都用Python了Bash还有必要学吗我的看法是轻量任务用Bash复杂逻辑用Python。部署脚本、日志清理、批量操作这些几十行Bash搞定的事没必要起个Python环境。而且很多时候服务器上就只有Bash你不得不用。脚本开头别省这几行#!/bin/bashset-euo pipefail# 脚本说明# 作者xxx# 日期2024-12-29# 用途xxxset -euo pipefail这行很重要-e命令失败立即退出不会继续执行后面的-u使用未定义变量报错避免typo导致的问题-o pipefail管道中任一命令失败整个管道返回失败没加这个的脚本经常是前面出错了后面还在跑最后一看结果全乱了。# 反面例子没有 set -ecd/data/backup# 这个目录不存在rm-rf *# 灾难发生...变量引号是个大坑永远用双引号包裹变量这是血泪教训。# 错误写法file_path/data/my file.txtrm$file_path# 实际执行: rm /data/my file.txt (删了两个文件!)# 正确写法file_path/data/my file.txtrm$file_path还有一个常见问题# 变量为空时的坑if[$nameadmin];then# name为空时语法错误echohi adminfi# 正确写法if[$nameadmin];thenechohi adminfi# 更推荐用双括号if[[$nameadmin]];thenechohi adminfi字符串操作不用awk也能干# 获取文件名path/data/logs/app.logfilename${path##*/}# app.logdirname${path%/*}# /data/logs# 字符串替换strhello world worldecho${str/world/bash}# hello bash world (只替换第一个)echo${str//world/bash}# hello bash bash (替换所有)# 提取子串strhello worldecho${str:0:5}# hello (从0开始取5个)echo${str:6}# world (从6开始到结尾)# 字符串长度echo${#str}# 11# 默认值echo${name:-default}# name为空用defaultecho${name:default}# name为空用default并赋值给name这些操作比调用外部命令快很多处理大量数据时差距明显。数组批量操作的基础# 定义数组servers(192.168.1.1192.168.1.2192.168.1.3)# 遍历forserverin${servers[]};doecho检查$serverping-c1$server/dev/nullechoOK||echoFAILdone# 数组长度echo共${#servers[]}台服务器# 添加元素servers(192.168.1.4)# 取特定元素echo第一台:${servers[0]}# 取所有索引foriin${!servers[]};doecho索引$i:${servers[$i]}done实际应用批量部署#!/bin/bashset-euo pipefailservers(web1web2web3)packageapp-v2.0.tar.gzforserverin${servers[]};doecho 部署到$serverscp$package$server:/tmp/ssh$servercd /tmp tar xzf$package ./install.shecho$server完成 done条件判断方括号的玄学Bash的条件判断语法挺乱的我整理个对照表# 字符串比较[[$a$b]]# 相等[[$a!$b]]# 不等[[-z$a]]# 为空[[-n$a]]# 不为空# 数值比较[[$a-eq$b]]# 等于[[$a-ne$b]]# 不等于[[$a-gt$b]]# 大于[[$a-lt$b]]# 小于[[$a-ge$b]]# 大于等于[[$a-le$b]]# 小于等于# 或者用双括号做算术比较((ab))((ab))# 文件判断[[-f$file]]# 是普通文件[[-d$dir]]# 是目录[[-e$path]]# 存在[[-r$file]]# 可读[[-w$file]]# 可写[[-x$file]]# 可执行[[-s$file]]# 文件大小0# 逻辑运算[[$a$b]]# 与[[$a||$b]]# 或[[!$a]]# 非为什么推荐双括号[[]]而不是单括号[]# 单括号的坑name[$nameadmin]# 语法错误[ admin ]# 双括号没问题[[$nameadmin]]# 正常工作# 单括号要转义[$a\$b]# 字符串比较大于# 双括号不用[[$a$b]]函数写可复用的代码# 基本写法log(){locallevel$1localmessage$2echo[$(date%Y-%m-%d %H:%M:%S)] [$level]$message}logINFO脚本启动logERROR出错了# 返回值check_service(){localservice$1systemctl is-active$service/dev/nullreturn$?# 返回上个命令的退出码}ifcheck_service nginx;thenechonginx 正在运行elseechonginx 未运行fi# 返回字符串通过echoget_ip(){hostname-I|awk{print $1}}my_ip$(get_ip)echo本机IP:$my_ip注意local关键字函数内的变量如果不加local会变成全局变量很容易出问题# 坑count10add_count(){count20# 修改了全局变量}add_countecho$count# 20不是10# 正确做法add_count(){localcount20# 局部变量}参数处理让脚本更专业简单参数用位置变量#!/bin/bash# usage: ./deploy.sh env versionenv${1:-prod}# 第一个参数默认prodversion${2:-latest}# 第二个参数默认latestecho部署$version到$env环境复杂参数用getopts#!/bin/bashset-euo pipefailusage(){catEOF 用法:$0[选项] 选项: -e, --env ENV 环境 (prod/test) -v, --version VER 版本号 -f, --force 强制执行 -h, --help 帮助 EOFexit1}# 默认值envprodversionlatestforcefalse# 解析参数while[[$#-gt0]];docase$1in-e|--env)env$2shift2;;-v|--version)version$2shift2;;-f|--force)forcetrueshift;;-h|--help)usage;;*)echo未知参数:$1usage;;esacdoneecho环境:$envecho版本:$versionecho强制:$force错误处理优雅地失败#!/bin/bashset-euo pipefail# 清理函数cleanup(){localexit_code$?echo清理临时文件...rm-rf$tmp_dir2/dev/null||trueexit$exit_code}# 注册退出时执行trapcleanup EXIT# 错误处理error_handler(){echo错误发生在第$1行exit1}traperror_handler $LINENOERR# 临时目录tmp_dir$(mktemp -d)echo临时目录:$tmp_dir# 你的逻辑...trap是个好东西常用信号EXIT脚本退出时ERR命令出错时INTCtrlC时TERMkill时实战日志清理脚本#!/bin/bashset-euo pipefail# 配置LOG_DIR/var/log/appKEEP_DAYS7MAX_SIZE_MB100log(){echo[$(date%Y-%m-%d %H:%M:%S)]$1}# 删除N天前的日志clean_old_logs(){localcountcount$(find$LOG_DIR-name*.log-mtime$KEEP_DAYS|wc-l)if[[$count-gt0]];thenlog删除$count个${KEEP_DAYS}天前的日志find$LOG_DIR-name*.log-mtime$KEEP_DAYS-deleteelselog没有需要删除的旧日志fi}# 压缩大日志compress_large_logs(){localmax_size$((MAX_SIZE_MB*1024*1024))whileIFSread-r -dfile;dolocalsizesize$(stat-f%z$file2/dev/null||stat-c%s$file)if[[$size-gt$max_size]];thenlog压缩大文件:$file($((size/1024/1024))MB)gzip$filefidone(find$LOG_DIR-name*.log-print0)}# 主逻辑main(){log 日志清理开始 if[[!-d$LOG_DIR]];thenlog目录不存在:$LOG_DIRexit1ficlean_old_logs compress_large_logs log 日志清理完成 }main$实战服务健康检查#!/bin/bashset-euo pipefail# 配置SERVICES(nginxmysqlredis)WEBHOOK_URLhttps://your-webhook-urlCHECK_INTERVAL60send_alert(){localmessage$1# 发送告警根据实际情况对接企业微信/钉钉/飞书curl-s -X POST$WEBHOOK_URL\-HContent-Type: application/json\-d{\text\:\$message\}/dev/null||true}check_service(){localservice$1ifsystemctl is-active$service/dev/null;thenreturn0elsereturn1fi}check_port(){localhost$1localport$2ifnc-z -w3$host$port/dev/null;thenreturn0elsereturn1fi}main(){localfailed_services()forservicein${SERVICES[]};doif!check_service$service;thenfailed_services($service)fidoneif[[${#failed_services[]}-gt0]];thenlocalmessage[告警]$(hostname)服务异常:${failed_services[*]}echo$messagesend_alert$messageelseecho所有服务正常fi}# 单次检查或持续监控if[[${1:-}--daemon]];thenwhiletrue;domainsleep$CHECK_INTERVALdoneelsemainfi调试技巧# 方法1打印执行的每条命令bash-x script.sh# 方法2在脚本里开启set-x# 开启调试# ... 你的代码 ...setx# 关闭调试# 方法3只调试一部分#!/bin/bashecho正常输出set-x problematic_functionsetxecho继续正常# 方法4打印变量echoDEBUG: var$var2常见坑汇总1. 空格问题# 错误varvalue# 赋值等号两边不能有空格if[$a$b]# 判断等号两边必须有空格# 正确varvalueif[$a$b]2. 路径中的特殊字符# 永远用引号包路径forfilein$dir/*;doprocess$filedone3. 管道中的变量# 坑管道在子shell执行变量改不了count0catfile.txt|whilereadline;do((count))doneecho$count# 还是0# 正确用进程替换count0whilereadline;do((count))donefile.txtecho$count# 正确的值4. 命令替换中的换行# 换行会变成空格files$(ls)echo$files# 保留换行echo$files# 变成一行总结写Bash脚本的几个原则开头加set -euo pipefail早发现问题变量一律用引号包避免空格和空值问题用双括号[[]]做条件判断函数内变量用local写注释一个月后你自己都看不懂加日志出问题好排查Bash不是银弹超过200行就该考虑Python了。但对于日常运维的小工具Bash足够好用。有问题评论区聊我尽量回。