
1. Shell脚本入门从Hello World到实用工具刚接触Shell脚本时很多人会被各种符号和命令吓到。但就像学骑自行车一样一旦掌握了基本平衡剩下的就是不断练习了。我刚开始写Shell脚本时最头疼的就是那个神秘的#!/bin/bash——它到底有什么用后来才发现这就像给剧本指定导演告诉系统该用哪个解释器来执行这个脚本。让我们从最基础的脚本开始。创建一个hello.sh文件内容如下#!/bin/bash echo Hello World保存后你可以用三种方式运行它bash hello.sh直接调用解释器sh hello.sh使用sh解释器chmod x hello.sh ./hello.sh赋予执行权限后直接运行提示新手常犯的错误是忘记给脚本执行权限就直接运行。如果看到Permission denied错误记得先用chmod x命令授权。变量是Shell脚本的基石。定义变量时要注意等号两边不能有空格变量名建议全大写。比如SERVER_IP192.168.1.1 echo 正在连接服务器 $SERVER_IP...这里有个实用技巧用readonly声明不可修改的常量用export将变量提升为全局环境变量。我在实际项目中就吃过亏——一个测试脚本意外修改了生产环境配置就是因为变量没加readonly限制。2. 条件判断与循环控制让脚本学会做决策真实的运维场景中脚本需要根据不同条件执行不同操作。比如检查磁盘空间时超过90%就该报警否则只需记录日志。这就是条件判断的用武之地。Shell提供了两种条件判断语法test命令test -f /path/to/file方括号[ -f /path/to/file ]注意括号内外的空格常用判断条件包括文件检测-e(存在)、-f(是文件)、-d(是目录)数值比较-eq(等于)、-gt(大于)字符串比较(相等)、!(不等)来看个实际案例——自动备份数据库前检查磁盘空间#!/bin/bash FREE_SPACE$(df -h /backup | awk NR2 {print $5} | tr -d %) WARNING_THRESHOLD90 if [ $FREE_SPACE -ge $WARNING_THRESHOLD ]; then echo 警告备份分区剩余空间不足当前使用率: ${FREE_SPACE}% exit 1 else echo 磁盘空间正常开始备份... # 这里放备份命令 fi循环则是处理批量任务的利器。比如批量检查服务器连通性#!/bin/bash SERVERS(192.168.1.10 192.168.1.11 192.168.1.12) for IP in ${SERVERS[]}; do if ping -c 1 $IP /dev/null; then echo $IP 在线 else echo $IP 离线 2 fi done注意for循环处理带空格的字符串时会出问题。安全做法是用数组和双引号包裹变量就像上面例子那样。3. 函数与参数处理打造可复用的脚本组件当脚本超过50行时就该考虑用函数来组织代码了。函数不仅能提高可读性还能避免重复代码。Shell中定义函数有两种方式# 方式一 function check_disk() { local threshold$1 # local表示局部变量 # 函数体 } # 方式二 check_memory() { # 函数体 }关键技巧用local声明局部变量避免污染全局命名空间通过$1、$2等获取参数用return返回退出状态码0表示成功分享一个实用的日志函数我在多个项目中都复用这个组件#!/bin/bash LOG_FILE/var/log/my_script.log log() { local level$1 local message$2 local timestamp$(date %Y-%m-%d %H:%M:%S) case $level in INFO) echo [$timestamp] INFO: $message $LOG_FILE ;; WARN) echo [$timestamp] WARN: $message $LOG_FILE ;; ERROR) echo [$timestamp] ERROR: $message $LOG_FILE 2 ;; esac } log INFO 脚本启动 # 业务逻辑... log WARN 磁盘空间接近阈值参数处理是另一个重点。特殊变量如$#(参数个数)、$(所有参数)非常有用。这里有个处理命令行选项的模板while getopts u:p:h opt; do case $opt in u) USERNAME$OPTARG ;; p) PASSWORD$OPTARG ;; h) echo 用法: $0 -u 用户名 -p 密码; exit 0 ;; *) echo 无效选项; exit 1 ;; esac done4. 实战案例自动化运维三剑客现在我们把前面学的知识综合运用实现三个经典运维场景。4.1 日志分析报警系统这个脚本会扫描指定日志文件匹配错误关键词超过阈值就发邮件报警#!/bin/bash LOG_FILE/var/log/nginx/error.log ERROR_KEYWORDS(502 Bad Gateway 404 Not Found) THRESHOLD10 EMAILadminexample.com analyze_log() { local keyword$1 local count$(grep -c $keyword $LOG_FILE) if [ $count -gt $THRESHOLD ]; then send_alert $keyword 出现 $count 次 fi } send_alert() { local content$1 echo $content | mail -s 日志报警 $EMAIL } for keyword in ${ERROR_KEYWORDS[]}; do analyze_log $keyword done4.2 自动化备份方案这个备份脚本特点保留最近7天备份自动压缩校验备份完整性#!/bin/bash BACKUP_DIR/backup DB_USERroot DB_PASSpassword DATE$(date %Y%m%d) KEEP_DAYS7 # 创建备份目录 [ -d $BACKUP_DIR ] || mkdir -p $BACKUP_DIR # MySQL备份 mysqldump -u$DB_USER -p$DB_PASS --all-databases | gzip $BACKUP_DIR/db_$DATE.sql.gz # 校验备份 if [ ${PIPESTATUS[0]} -ne 0 ]; then echo 数据库备份失败 2 exit 1 fi # 清理旧备份 find $BACKUP_DIR -name *.sql.gz -mtime $KEEP_DAYS -delete4.3 服务监控与自愈这个脚本监控Nginx服务异常时自动重启#!/bin/bash SERVICEnginx MAX_RETRY3 LOG/var/log/service_monitor.log check_service() { if ! systemctl is-active --quiet $SERVICE; then echo $(date) - $SERVICE 服务停止 $LOG restart_service fi } restart_service() { local retry0 while [ $retry -lt $MAX_RETRY ]; do systemctl restart $SERVICE sleep 5 if systemctl is-active --quiet $SERVICE; then echo $(date) - 重启成功 $LOG return 0 fi ((retry)) done echo $(date) - 重启失败已达最大重试次数 $LOG send_alert $SERVICE 服务无法启动 } check_service5. 高级技巧与避坑指南5.1 安全防护要点写生产环境脚本时安全必须放在第一位用set -e让脚本遇到错误立即退出用set -u防止使用未定义变量用set -o pipefail确保管道命令中任意失败都会终止脚本完整的安全头部应该这样写#!/bin/bash set -euo pipefail IFS$\n\t5.2 性能优化技巧处理大文件或循环时这些技巧能显著提升性能避免在循环中调用外部命令改用Shell内置功能使用awk/sed代替grepcut组合批量操作时尽量减少文件IO比如统计日志中IP访问次数低效写法cat access.log | cut -d -f1 | sort | uniq -c高效写法awk {ips[$1]} END {for(ip in ips) print ips[ip], ip} access.log | sort -nr5.3 调试与排错调试Shell脚本的几种方法打印调试信息set -x开启调试模式检查退出状态echo $?使用ShellCheck在线工具检查语法这是我常用的调试模板#!/bin/bash set -x # 开启调试 # 业务代码... set x # 关闭调试遇到复杂问题时可以分段调试在可疑代码前加set -x在可疑代码后加set x使用trap命令捕获信号进行清理6. 实战项目从零构建自动化运维系统让我们用所学知识构建一个完整的自动化运维系统包含以下功能定时执行批量任务集中管理多台服务器可视化报告生成6.1 架构设计├── bin/ │ ├── deploy.sh # 部署脚本 │ ├── monitor.sh # 监控脚本 │ └── backup.sh # 备份脚本 ├── config/ │ └── servers.list # 服务器列表 ├── lib/ │ └── utils.sh # 通用函数库 └── logs/ # 日志目录6.2 核心实现lib/utils.sh通用函数库#!/bin/bash # 颜色定义 RED\033[0;31m GREEN\033[0;32m NC\033[0m # No Color # 日志函数 log() { local level$1 shift local message$ local timestamp$(date %Y-%m-%d %H:%M:%S) case $level in INFO) echo -e ${GREEN}[$timestamp] INFO: $message${NC} ;; ERROR) echo -e ${RED}[$timestamp] ERROR: $message${NC} 2 ;; esac } # 远程执行命令 remote_exec() { local server$1 local cmd$2 ssh -o ConnectTimeout5 $server $cmd return $? }bin/monitor.sh监控脚本#!/bin/bash source ../lib/utils.sh SERVER_LIST../config/servers.list while read server; do log INFO 检查服务器: $server # 检查磁盘 disk_usage$(remote_exec $server df -h / | awk NR2 {print \$5}) if [[ $disk_usage ~ ([0-9])% ]]; then usage${BASH_REMATCH[1]} if [ $usage -gt 90 ]; then log ERROR $server 磁盘使用率过高: $usage% fi fi # 检查内存 mem_free$(remote_exec $server free -m | awk /Mem/{print \$4}) [ $mem_free -lt 100 ] log WARN $server 内存不足剩余: ${mem_free}MB done $SERVER_LIST6.3 部署与调度用crontab设置定时任务# 每天凌晨执行备份 0 0 * * * /opt/ops/bin/backup.sh # 每5分钟执行监控 */5 * * * * /opt/ops/bin/monitor.sh # 每周一执行安全更新 0 3 * * 1 /opt/ops/bin/update.sh7. 从脚本到系统工程化实践当脚本越来越多时就需要考虑工程化管理了。以下是我总结的最佳实践版本控制所有脚本必须纳入Git管理代码审查重要的脚本变更需要同行评审单元测试用bats框架为关键函数写测试文档规范每个脚本头部要有标准注释#!/bin/bash # 名称: monitor.sh # 描述: 服务器健康状态监控 # 作者: Your Name # 版本: 1.0 # 参数: 无 # 返回值: 0-成功 1-失败配置分离将配置项抽离到单独文件日志规范统一日志格式和存储位置报警集成对接企业微信/钉钉/Slack等IM工具分享一个通过企业微信发送报警的实用函数send_wechat() { local corp_idYOUR_CORP_ID local corp_secretYOUR_SECRET local agent_idYOUR_AGENT_ID local msg$1 # 获取access_token token$(curl -s https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid$corp_idcorpsecret$corp_secret | jq -r .access_token) # 发送消息 curl -s -X POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token$token \ -H Content-Type: application/json \ -d { touser: all, msgtype: text, agentid: $agent_id, text: { content: $msg }, safe: 0 } }8. 性能监控脚本深度优化让我们深入优化一个服务器性能监控脚本使其具备实时监控CPU、内存、磁盘历史数据存储自动生成报告优化后的脚本架构#!/bin/bash # 性能监控v2.0 # 加载库 source ./lib/metrics.sh source ./lib/report.sh # 主循环 while true; do # 采集数据 cpu_usage$(get_cpu_usage) mem_usage$(get_mem_usage) disk_usage$(get_disk_usage) # 存储数据 store_metrics $cpu_usage $mem_usage $disk_usage # 检查阈值 check_thresholds $cpu_usage $mem_usage $disk_usage # 间隔60秒 sleep 60 donelib/metrics.sh采集函数#!/bin/bash # 获取CPU使用率(取1秒内的平均值) get_cpu_usage() { local cpu$(top -bn1 | grep Cpu(s) | sed s/.*, *\([0-9.]*\)%* id.*/\1/ | awk {print 100 - $1}) printf %.1f $cpu } # 获取内存使用率 get_mem_usage() { local total$(free -m | awk /Mem/{print $2}) local used$(free -m | awk /Mem/{print $3}) local percent$(awk BEGIN {printf \%.1f\, $used/$total*100}) echo $percent } # 获取磁盘使用率 get_disk_usage() { df -h / | awk NR2 {print $5} | tr -d % }数据存储采用CSV格式便于后续分析2023-08-01 12:00:00,25.5,68.3,45 2023-08-01 12:01:00,26.1,67.9,45报告生成使用gnuplot绘制趋势图#!/bin/bash generate_report() { local data_file$1 local output_img$2 gnuplot - EOF set terminal png size 1000,600 set output $output_img set xdata time set timefmt %Y-%m-%d %H:%M:%S set format x %H:%M set xlabel 时间 set ylabel 使用率(%) set title 服务器性能监控报告 - $(date %Y-%m-%d) plot $data_file using 1:2 with lines title CPU, \ $data_file using 1:3 with lines title 内存, \ $data_file using 1:4 with lines title 磁盘 EOF }9. 错误处理与日志收集系统健壮的运维系统必须有完善的错误处理和日志收集机制。以下是一个分布式日志收集方案客户端脚本部署在各服务器上#!/bin/bash # log_collector.sh LOG_DIR/var/log/app CENTRAL_SERVERlogserver.example.com ARCHIVE_DIR/backup/logs # 发送日志到中心服务器 send_logs() { local log_file$1 local server$2 if ! scp $log_file ops$server:/collector/raw/; then echo 日志传输失败: $log_file 2 return 1 fi # 传输成功后归档 mv $log_file $ARCHIVE_DIR/ } # 处理旧日志 for old_log in $(find $LOG_DIR -name *.log -mtime 1); do gzip $old_log send_logs ${old_log}.gz $CENTRAL_SERVER done服务端处理脚本日志中心服务器#!/bin/bash # log_processor.sh RAW_DIR/collector/raw PROCESSED_DIR/collector/processed REPORT_DIR/collector/reports process_log() { local log_file$1 local server_name$(basename $log_file | cut -d_ -f1) # 错误统计 local errors$(zgrep -c ERROR $log_file) local warnings$(zgrep -c WARN $log_file) # 生成报告 echo $server_name,$(date %Y-%m-%d),$errors,$warnings $REPORT_DIR/daily_report.csv # 移动已处理文件 mv $log_file $PROCESSED_DIR/ } # 主循环 while true; do for new_log in $(find $RAW_DIR -name *.log.gz); do process_log $new_log done sleep 300 done日志分析报表生成#!/bin/bash # report_generator.sh REPORT_DATA$REPORT_DIR/daily_report.csv HTML_REPORT$REPORT_DIR/latest.html generate_html_report() { echo htmlbodyh1服务器日志分析报告/h1 $HTML_REPORT echo table border1trth服务器/thth日期/thth错误数/thth警告数/th/tr $HTML_REPORT while IFS, read -r server date errors warnings; do echo trtd$server/tdtd$date/tdtd$errors/tdtd$warnings/td/tr $HTML_REPORT done $REPORT_DATA echo /table/body/html $HTML_REPORT }10. 安全加固与权限管理生产环境中的Shell脚本必须考虑安全性。以下是关键防护措施最小权限原则为脚本创建专用用户# 创建运维专用用户 sudo useradd -m -s /bin/bash opsadmin sudo passwd opsadmin # 设置sudo权限/etc/sudoers opsadmin ALL(ALL) NOPASSWD: /opt/scripts/*敏感信息保护不要硬编码密码# 错误做法 DB_PASS123456 # 正确做法 # 从加密文件读取 DB_PASS$(gpg --decrypt secrets.gpg) # 或者使用环境变量 export DB_PASS$(vault kv get -fieldpassword secret/db)输入验证防止命令注入# 危险可能被注入 read -p 输入服务器IP: IP ping $IP # 安全做法 read -p 输入服务器IP: IP if [[ $IP ~ ^[0-9]\.[0-9]\.[0-9]\.[0-9]$ ]]; then ping -c 1 $IP else echo 非法IP地址 fi日志审计记录关键操作# 记录谁在什么时候执行了什么 log_audit() { local user$(whoami) local timestamp$(date %Y-%m-%d %H:%M:%S) local action$1 echo $timestamp $user $action /var/log/audit.log } # 使用示例 log_audit 执行了数据库备份文件完整性检查防止脚本被篡改#!/bin/bash # verify_script.sh SCRIPT/opt/scripts/backup.sh KNOWN_HASHa1b2c3d4e5f6... current_hash$(sha256sum $SCRIPT | awk {print $1}) if [ $current_hash ! $KNOWN_HASH ]; then echo 警告脚本已被修改 exit 1 fi11. 跨平台兼容性处理不同Linux发行版甚至Unix-like系统之间Shell语法和工具链可能存在差异。以下是保证兼容性的技巧指定解释器版本#!/bin/bash --posix # 使用POSIX模式工具检测与替代方案# 检测工具是否存在 if ! command -v jq /dev/null; then echo 错误需要jq但未安装 exit 1 fi # 替代方案示例如果没有awk使用sed if command -v awk /dev/null; then CPU_USAGE$(top -bn1 | awk /Cpu/{print $2}) else CPU_USAGE$(top -bn1 | sed -n /Cpu/s/.*, *\([0-9.]*\)%* id.*/\1/p) fi路径处理兼容性# 获取脚本所在目录兼容各种系统 SCRIPT_DIR$(cd $(dirname ${BASH_SOURCE[0]}) /dev/null pwd) # 路径拼接使用变量 CONFIG_FILE$SCRIPT_DIR/../config/settings.cfg系统类型判断case $(uname -s) in Linux*) SYSTEMLinux ;; Darwin*) SYSTEMMac ;; CYGWIN*) SYSTEMCygwin ;; MINGW*) SYSTEMMinGw ;; *) SYSTEMUnknown ;; esac if [ $SYSTEM Linux ]; then # Linux特有命令 PKG_MANAGERapt-get elif [ $SYSTEM Mac ]; then # Mac特有命令 PKG_MANAGERbrew fi时间处理兼容方案# GNU date和BSD date参数不同 if date --version /dev/null; then # GNU date TIMESTAMP$(date -d 1 day ago %Y-%m-%d) else # BSD date (Mac) TIMESTAMP$(date -v-1d %Y-%m-%d) fi12. 性能敏感场景的优化策略当脚本需要处理大量数据或高频执行时性能优化就变得至关重要。以下是我在实战中总结的优化技巧减少子进程创建# 低效每次循环都调用date for i in {1..1000}; do echo $(date %s) - $i done # 高效只调用一次date now$(date %s) for i in {1..1000}; do echo $now - $i done使用内置字符串操作代替外部命令# 低效使用cut echo hello:world | cut -d: -f1 # 高效使用Shell内置 strhello:world echo ${str%:*}批量处理代替逐行处理# 低效逐行处理 while read line; do process $line done file.txt # 高效批量读取 mapfile -t lines file.txt for line in ${lines[]}; do process $line done使用更快的文本处理工具# 处理GB级日志文件时 # grepawk组合约30秒 grep ERROR huge.log | awk {print $3} # 单独使用awk约15秒 awk /ERROR/{print $3} huge.log # 使用ripgrep(rg)约5秒 rg ERROR huge.log -o | awk {print $3}并行处理加速# 串行处理 for server in ${SERVERS[]}; do check_status $server done # 并行处理使用GNU parallel或xargs printf %s\n ${SERVERS[]} | xargs -P 4 -I {} bash -c check_status $ _ {}内存优化技巧# 处理大文件时避免全部读入内存 while IFS read -r line; do process_line $line done (grep pattern huge_file.txt) # 使用临时文件代替变量存储中间结果 awk {print $1} big_file.txt temp_file1 awk {print $2} big_file.txt temp_file2 # 而不是 # col1$(awk {print $1} big_file.txt) # col2$(awk {print $2} big_file.txt)13. 与现代运维工具集成传统Shell脚本可以很好地与现代运维工具链集成发挥更大价值与Ansible集成#!/bin/bash # 动态生成Ansible inventory echo [web] aws ec2 describe-instances \ --filters Nametag:Role,Valuesweb \ --query Reservations[].Instances[].PrivateIpAddress \ --output text | tr \t \n echo [db] aws ec2 describe-instances \ --filters Nametag:Role,Valuesdb \ --query Reservations[].Instances[].PrivateIpAddress \ --output text | tr \t \n与Docker/Kubernetes集成#!/bin/bash # 自动部署容器并检查状态 DEPLOYMENTmyapp IMAGE_VERSIONv1.2.3 NAMESPACEproduction # 部署 kubectl set image deployment/$DEPLOYMENT *myrepo/myapp:$IMAGE_VERSION -n $NAMESPACE # 等待就绪 for i in {1..30}; do ready$(kubectl get deploy $DEPLOYMENT -n $NAMESPACE -o jsonpath{.status.readyReplicas}) if [ $ready -eq 3 ]; then echo 部署成功 exit 0 fi sleep 5 done echo 部署超时 exit 1与CI/CD管道集成#!/bin/bash # GitLab CI Runner脚本 # 构建阶段 build() { docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA } # 测试阶段 test() { docker run --rm $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA npm test } # 部署阶段 deploy() { kubectl set image deployment/myapp *myapp:$CI_COMMIT_SHA -n production } case $1 in build) build ;; test) test ;; deploy) deploy ;; *) echo 无效阶段 ;; esac与监控系统集成#!/bin/bash # 向Prometheus Pushgateway提交指标 PUSHGATEWAYprometheus.example.com:9091 JOB_NAMEcustom_metrics # 收集指标 cpu_temp$(sensors | awk /Package/{print $4} | tr -d °C) disk_usage$(df -h / | awk NR2 {print $5} | tr -d %) # 推送指标 cat EOF | curl --data-binary - http://$PUSHGATEWAY/metrics/job/$JOB_NAME # TYPE cpu_temperature gauge cpu_temperature $cpu_temp # TYPE disk_usage_percent gauge disk_usage_percent $disk_usage EOF14. 复杂文本处理实战Shell脚本配合文本处理工具可以高效解决复杂日志分析需求多维度日志分析#!/bin/bash # 分析Nginx日志统计状态码、热门URL、客户端IP LOG_FILE/var/log/nginx/access.log # 状态码统计 echo 状态码统计: awk {print $9} $LOG_FILE | sort | uniq -c | sort -nr # 热门URL echo -e \n热门URL: awk -F {print $2} $LOG_FILE | awk {print $2} | sort | uniq -c | sort -nr | head -10 # 客户端IP统计 echo -e \n客户端IP统计: awk {print $1} $LOG_FILE | sort | uniq -c | sort -nr | head -20实时日志监控#!/bin/bash # 实时监控错误日志并报警 LOG_FILE/var/log/app/error.log ALERT_THRESHOLD5 ALERT_EMAILadminexample.com tail -Fn0 $LOG_FILE | \ while read line; do if echo $line | grep -q ERROR; then echo $line /tmp/error_buffer.log error_count$(wc -l /tmp/error_buffer.log) if [ $error_count -ge $ALERT_THRESHOLD ]; then mail -s 应用错误报警 $ALERT_EMAIL /tmp/error_buffer.log /tmp/error_buffer.log fi fi doneCSV文件处理#!/bin/bash # 处理员工薪资CSV文件 INPUTemployees.csv OUTPUTreport.txt # 计算各部门平均薪资 awk -F, NR1 { dept[$3] $4 count[$3] } END { for(d in dept) { avg dept[d]/count[d] printf %s: %.2f\n, d, avg } } $INPUT | sort -k2 -nr $OUTPUT # 添加报表头部 sed -i 1i 部门平均薪资报告\n $OUTPUTJSON数据处理#!/bin/bash # 处理API返回的JSON数据 API_URLhttps://api.example.com/data TEMP_FILE/tmp/api_response.json # 获取并处理JSON curl -s $API_URL | jq . $TEMP_FILE # 提取关键信息 total_items$(jq .pagination.total $TEMP_FILE) first_item$(jq .data[0].name $TEMP_FILE) echo 共 $total_items 条记录第一条: $first_item # 复杂处理过滤并转换数据 jq -r .data[] | select(.value 100) | \(.name):\(.value) $TEMP_FILE | \ while IFS: read -r name value; do echo 高价值项目: $name (值: $value) done15. 系统管理自动化合集分享几个我在日常运维中积累的实用脚本用户账户批量管理#!/bin/bash # 批量创建用户并设置随机密码 USER_LISTusers.txt PASSWORD_LENGTH12 while IFS: read -r username groups; do # 创建用户 if ! id $username /dev/null; then useradd -m -s /bin/bash $username # 设置组 if [ -n $groups ]; then usermod -aG $groups $username fi # 生成随机密码 password$(tr -dc A-Za-z0-9 /dev/urandom | head -c $PASSWORD_LENGTH) echo $username:$password | chpasswd # 记录密码实际环境应该使用密钥或密码管理器 echo $username:$password:$groups created_users.log else echo 用户 $username 已存在 2 fi done $USER_LISTSSL证书监控#!/bin/bash # 检查证书过期时间 DOMAINS(example.com api.example.com blog.example.com) WARNING_DAYS30 for domain in ${DOMAINS[]}; do # 获取证书过期时间 expiry_date$(echo | openssl s_client -connect $domain:443 2/dev/null | \ openssl x509 -noout -dates | grep ^notAfter | cut -d -f2) # 转换为时间戳 expiry_epoch$(date -d $expiry_date %s) now_epoch$(date %s) days_left$(( (expiry_epoch - now_epoch) / 86400 )) # 判断是否需要报警 if [ $days_left -le $WARNING_DAYS ]; then echo 警告: $