1. 为什么把函数单独拎出来讲
1.1 函数是Shell脚本从"命令流水账"到"程序"的分水岭
前面几篇我们一直在处理命令、变量、条件判断和循环,说白了还是在写"命令流水账"。真正让Shell脚本具备工程价值的,是函数。没有函数之前,你处理Nginx日志分析要写一遍循环,处理配置文件备份又要写一遍循环,两个循环逻辑类似但散落各处,改一处忘一处的事情我干过不止一次。有了函数之后,同一个逻辑定义一次,后面反复调用,脚本的可读性和可维护性完全是两个档次。
很多刚接触linux-shell的人会觉得函数是"高级用法",其实恰恰相反,函数是最朴素的代码复用工具。你自己写脚本超过300行之后,如果没有函数,基本就是一场灾难:变量互相污染、修改需求时找不到该改哪里、调试时得从头跟到尾。有了函数,每个功能模块独立成块,输入输出清晰,单测起来也方便。
1.2 这次实战为什么选Nginx
光讲语法没意思,语法书和man手册里都有。我坚持一个观点:Shell函数的每个知识点,最好都能挂在一个真实的运维场景里去理解,否则学完就忘。
选Nginx做实战对象,原因很简单:
- Nginx是现在Web服务的事实标准,绝大多数Linux服务器上都跑着它,场景足够真实。
- Nginx的日常运维动作十分固定:启动、停止、重载、检查配置、备份配置、多站点管理,这些动作天然适合用函数封装。
- 操作Nginx有明确的"成功/失败"反馈(比如配置文件语法是否正确、进程是否存活),非常适合用来理解Shell函数里return值、echo输出、exit code这些概念。
换句话说,这篇不是纯粹的语法教学,而是"函数语法 + Nginx日常运维"的组合实战。你学完不但能看懂别人写的Nginx管理脚本,自己也能封装出一套好用的工具函数。
2. Shell函数的基本语法与设计要点
2.1 定义、调用与函数名的讲究
Shell函数的定义有两种写法:
# 写法一:function关键字,可读性好 function nginx_status() { systemctl status nginx } # 写法二:直接写函数名,POSIX风格,可移植性更强 nginx_status() { systemctl status nginx }两种写法我都用过,个人建议在脚本里统一用第二种。原因是在不同发行版、不同Shell环境下,function关键字的兼容性偶尔会有小问题,而name() {}这种写法从Bash到Dash到Zsh都能识别。
调用函数就一句话,直接写函数名:
nginx_status不需要加括号。很多从其他语言转过来的朋友总习惯写nginx_status()来调用,这个在Shell里是语法错误,Shell解析器会认为你在定义一个函数。
还有一个很实际的点:函数名不要跟系统命令重名。很多初学者喜欢给检查Nginx配置的函数起名check,结果发现脚本里其他地方想调用系统自带的check命令时行为变得诡异。我给函数命名喜欢加前缀,比如ngx_开头,这样既统一又不容易冲突:
ngx_config_check() { ... } ngx_server_status() { ... }2.2 参数传递:$1、$2、$@和$FUNCNAME
函数可以接收参数,这一点跟脚本本身接收参数的方式完全一样:
ngx_reload() { local bin_path="$1" echo "准备重载 ${bin_path}" "${bin_path}" -t "${bin_path}" -s reload } # 调用时直接在函数名后面跟参数 ngx_reload /usr/local/nginx/sbin/nginx这在设计上有一个非常关键的"为什么":如果你的Nginx是编译安装的,nginx二进制路径可能不在默认的/usr/sbin/nginx,而是自定义的/usr/local/nginx/sbin/nginx。把路径作为参数传进去,函数就不用写死路径,复用性大幅提高。
函数内部获取参数的变量:
| 变量 | 作用 | 示例 |
|---|---|---|
$0 | 当前脚本名(注意:不是函数名) | nginx_mgr.sh |
$1~$9 | 第1到第9个参数 | $1是/usr/local/nginx/sbin/nginx |
$# | 参数个数 | 传了2个参数就是2 |
$@ | 所有参数列表 | 可以循环遍历 |
$FUNCNAME | 当前函数名 | 调试时打印函数名极好用 |
这里面最大的坑是$0。在函数内部,$0仍然是脚本名,不是函数名。如果你想在日志里记录"当前是哪个函数在执行",得用${FUNCNAME[0]}:
ngx_reload() { echo "[DEBUG] 进入函数: ${FUNCNAME[0]}" }我调试复杂脚本时经常在关键函数入口加一行这个输出,可以快速定位逻辑走到哪一步了,比用set -x的全局跟踪清爽得多。
2.3 return值、echo输出与exit code的区别
这是Shell函数最容易混淆、也最影响实战效果的知识点。
Shell函数用return返回状态码(0表示成功,非0表示失败),注意返回的是状态码,不是字符串。同时,函数体内任何echo输出到标准输出的内容,都会被调用方捕获到。
看个反面教材:
# 错误示范:想返回字符串却用return get_nginx_pid() { local pid=$(pgrep -f 'nginx: master') return "$pid" # pid是数字,但超过255会被截断,且语义混乱 } # 推荐做法:用echo返回字符串,用return返回执行状态 get_nginx_pid() { local pid pid=$(pgrep -f 'nginx: master process') if [[ -n "$pid" ]]; then echo "$pid" return 0 else return 1 fi }这里引出一个实战中必须养成的习惯:同时使用echo和return时,要明确约定这个函数的"数据通道"和"状态通道"互不干扰。echo走的stdout是数据,return返回的0/1是状态。调用方可以这样配合:
if pid=$(get_nginx_pid); then echo "Nginx主进程PID为: ${pid}" else echo "未找到Nginx主进程" fi注意,if pid=$(get_nginx_pid)这一步,变量赋值+判断函数返回状态,是Shell里非常经典的一行写法。前提是函数内echo输出的只有那一行数据,不要把调试信息也echo出来。
忠告:函数内部的调试信息,尽量输出到标准错误(
>&2),不要污染标准输出。否则你后面用result=$(函数名)接数据时,会接进来一堆乱七八糟的日志。
2.4 局部变量与全局变量的边界
Shell函数里默认所有变量都是全局的。也就是说,函数里写x=1,函数外也能看到x,这在复杂脚本里是灾难的根源。
正确的做法是:凡是函数内部用到的临时变量,一律用local声明:
ngx_config_check() { local conf_dir="$1" local conf_file local error_count=0 for conf_file in "${conf_dir}"/*.conf; do if ! nginx -t -c "${conf_file}" >/dev/null 2>&1; then ((error_count++)) fi done echo "发现 ${error_count} 个配置文件异常" return "${error_count}" }用local声明的变量在函数执行结束后自动销毁,函数内外互相隔离。我在实际项目里总结了一条铁律:函数超过5行,里面的变量必须全部加local,一个都不许裸奔。这样脚本写完了,把每个函数单独拎出来测试,完全不会受到外部环境干扰。
还有一个细节:local只能用在函数内部!如果你在脚本顶层(不在任何函数里)尝试用local,Bash会直接报local: can only be used in a function。这个报错我见过不少新手踩过,特地说一下。
3. 用函数重构一整套Nginx管理脚本
3.1 实战目标:把零散命令变成结构化工具
这是我给一台开发服务器梳理Nginx管理流程时,实际总结出的一个脚本框架。先看需求:
- 这台服务器上有两个Nginx实例:一个跑主站点(监听80/443),一个跑本地开发环境(监听8080),对应热词里说的"本地+虚拟机多端口Nginx开发环境多站点自定义域名配置"场景。
- 日常操作包括:启动、停止、重载、配置检查、状态查看、备份配置。
- 要求任何一步操作都能一眼看出成功或失败。
- 脚本要能同时兼容yum安装的nginx和编译安装的nginx。
基于这些需求,我写了一个ngx_mgr.sh,骨架如下:
#!/usr/bin/env bash # Nginx多实例管理脚本 NGINX_A=/usr/sbin/nginx NGINX_B=/usr/local/nginx/sbin/nginx CONF_A=/etc/nginx/nginx.conf CONF_B=/usr/local/nginx/conf/nginx.conf PID_A=/run/nginx.pid PID_B=/usr/local/nginx/logs/nginx.pid # 每个函数对应一个管理动作 ngx_start() { ... } ngx_stop() { ... } ngx_reload() { ... } ngx_config_check() { ... } ngx_status() { ... }为什么要把这些动作封装成函数,而不是直接写一大串命令?核心原因是职责分离。主流程只需要根据用户输入,决定调用哪个函数,每个函数内部自己负责各自的动作细节。以后想给某个函数加日志、加通知、加失败重试,只需要改那一个函数,不影响其他逻辑。
3.2 核心函数:启动、停止、重载的封装细节
先看启动函数:
ngx_start() { local nginx_bin="$1" local conf_path="$2" local pid_file="$3" # 如果已经在运行,直接返回,不重复启动 if [[ -f "${pid_file}" ]] && kill -0 "$(cat "${pid_file}")" 2>/dev/null; then echo "[SKIP] Nginx已在运行,PID=$(cat "${pid_file}")" return 0 fi # 启动前必须校验配置,配置有错宁可不启动 if ! "${nginx_bin}" -t -c "${conf_path}"; then echo "[ERROR] Nginx配置文件语法错误,拒绝启动" >&2 return 1 fi "${nginx_bin}" -c "${conf_path}" sleep 1 if [[ -f "${pid_file}" ]] && kill -0 "$(cat "${pid_file}")" 2>/dev/null; then echo "[OK] Nginx启动成功,PID=$(cat "${pid_file}")" return 0 else echo "[ERROR] Nginx启动后未检测到进程" >&2 return 1 fi }这里面有几个细节值得讲:
kill -0并不是要杀进程,而是探测进程是否存在。这个技巧在Shell里判断进程存活极常用,比pgrep快且稳。- 启动前先
nginx -t校验配置,是我踩过无数坑之后总结的底线原则。如果跳过校验直接启动,一旦配置有错,nginx进程活跃几秒就退出,排查起来费时费力。 sleep 1是给nginx一个启动缓冲时间,否则刚执行完启动命令立刻检查pid文件,有可能还没写出来。
停止函数的核心是用-s quit优雅退出还是用kill强杀?这里我推荐优先尝试优雅退出:
ngx_stop() { local nginx_bin="$1" local pid_file="$2" if [[ ! -f "${pid_file}" ]]; then echo "[SKIP] PID文件不存在,Nginx可能未运行" return 0 fi local pid pid=$(cat "${pid_file}") if ! kill -0 "${pid}" 2>/dev/null; then echo "[WARN] PID ${pid} 对应的进程不存在,清理残留PID文件" rm -f "${pid_file}" return 0 fi # 先尝试优雅退出,等待8秒 "${nginx_bin}" -s quit local i for i in $(seq 1 8); do if ! kill -0 "${pid}" 2>/dev/null; then echo "[OK] Nginx已优雅退出" rm -f "${pid_file}" return 0 fi sleep 1 done # 超时后强制退出 kill -9 "${pid}" 2>/dev/null echo "[WARN] 优雅退出超时,已强制结束进程" >&2 return 0 }这个函数展示了Shell里一个典型的"优雅降级"思路:先尝试温和的方式,设置超时;超时未果再动用强制手段。生产环境里直接kill -9nginx进程会导致正在处理的请求被硬切断,影响用户体验。nginx -s quit会让worker进程处理完当前请求再退出,代价是等待时间不定,所以必须配合超时机制。
重载函数相对简单,但有一个重要的检查点:
ngx_reload() { local nginx_bin="$1" local conf_path="$2" if ! "${nginx_bin}" -t -c "${conf_path}" >/dev/null 2>&1; then echo "[ERROR] 配置检查失败,取消重载" >&2 return 1 fi "${nginx_bin}" -s reload echo "[OK] Nginx配置已热加载" return 0 }注意重载前依然要先跑一遍nginx -t。如果配置文件写错了直接-s reload,nginx会拒绝加载新配置,但老配置还在生效,表面上服务没挂,实际上你修改的内容根本没起作用。这很容易造成"我改了我以为生效了"的假象。先校验再重载,可以把问题拦截在操作之前。
3.3 状态检测与健康检查函数:curl、ss和kill -0的组合
状态检测不能只看进程存在,还要看网络端口是否真的在监听,最终还要看HTTP请求是否正常返回。
ngx_health_check() { local url="$1" local expect_code="${2:-200}" local http_code http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 --max-time 5 "${url}") if [[ "${http_code}" == "${expect_code}" ]]; then echo "[OK] ${url} 返回 HTTP ${http_code}" return 0 else echo "[ERROR] ${url} 返回 HTTP ${http_code},期望 ${expect_code}" >&2 return 1 fi }这个函数我在很多服务器健康检查脚本里复用。-o /dev/null是为了丢弃响应体,-w "%{http_code}"只提取状态码,--connect-timeout和--max-time防止curl卡死。
配合端口监听检查:
ngx_port_check() { local port="$1" if ss -tlnp | grep -q ":${port} "; then echo "[OK] 端口 ${port} 正在监听" return 0 else echo "[ERROR] 端口 ${port} 未监听" >&2 return 1 fi }注意grep -q ":${port} "末尾的空格,这个细节很关键。如果不加空格,8080会匹配到80801、80808等端口。加了空格能精确匹配端口号边界。
把这两个函数组合起来,就能做一个相对完整的健康检查:
ngx_check_all() { local health_url="$1" local listen_port="$2" ngx_port_check "${listen_port}" || return 1 ngx_health_check "${health_url}" || return 1 echo "[OK] Nginx服务整体正常" return 0 }这里用到了|| return 1,意思是前面的函数返回值非0,就立刻终止当前函数并返回1。这是Shell里推荐的错误传播方式,比用if判断简洁得多。
3.4 多实例场景下的函数抽象
回到实战场景里的开发机:同一个机器上跑着80端口的主站和8080端口的开发站。如果每个实例都写一套启动/停止/重载,脚本很快就爆炸了。
正确做法是把"实例差异"收敛成参数:
NGINX_INSTANCES=( "prod|/usr/sbin/nginx|/etc/nginx/nginx.conf|/run/nginx.pid|http://localhost/health" "dev|/usr/local/nginx/sbin/nginx|/usr/local/nginx/conf/nginx.conf|/usr/local/nginx/logs/nginx.pid|http://localhost:8080/health" )每个实例用|分隔:实例名、二进制路径、配置文件路径、pid文件路径、健康检查URL。然后写一个统一的分发函数:
ngx_instance_action() { local instance_name="$1" local action="$2" local instance_info local nginx_bin conf_path pid_file health_url # 从NGINX_INSTANCES中找匹配的实例 instance_info=$(printf "%s\n" "${NGINX_INSTANCES[@]}" | grep "^${instance_name}|") if [[ -z "${instance_info}" ]]; then echo "[ERROR] 找不到实例: ${instance_name}" >&2 return 1 fi IFS='|' read -r _ nginx_bin conf_path pid_file health_url <<< "${instance_info}" case "${action}" in start) ngx_start "${nginx_bin}" "${conf_path}" "${pid_file}" ;; stop) ngx_stop "${nginx_bin}" "${pid_file}" ;; reload) ngx_reload "${nginx_bin}" "${conf_path}" ;; health) ngx_check_all "${health_url}" "${nginx_bin}" ;; *) echo "[ERROR] 不支持的动作: ${action}" >&2 return 2 ;; esac }这一段用到了几个有意思的点:
- 用数组存储多实例配置,而不是写死变量,新增一个实例只改一行数据。
IFS='|' read -r ... <<< "${instance_info}"的作用是把按|分隔的字符串拆成变量,注意前面用_吞掉第一个字段(实例名)。- case分支做动作分发,每个action对应前面定义好的一个函数。
到这里,函数的复用价值就很直观了:无论你加第3个、第4个Nginx实例,管理脚本的核心函数一个都不用改,只需要在NGINX_INSTANCES数组里加一行配置。
4. 函数在Nginx配置生成与多站点域名解析中的应用
4.1 配置生成函数:把重复的server块封装起来
除了控制Nginx进程,Shell函数另一个高频应用场景是生成配置文件。尤其是开发环境,经常要新增一个站点的nginx配置:一个server块、监听一个端口、指向一个目录、配一个域名。手写一次两次还行,次数多了又慢又容易抄错。
我用函数封装了server配置生成逻辑:
ngx_gen_site_conf() { local domain="$1" local port="$2" local root_dir="$3" local conf_file="$4" cat > "${conf_file}" <<EOF server { listen ${port}; server_name ${domain}; root ${root_dir}; index index.html index.htm; access_log /var/log/nginx/${domain}.access.log; error_log /var/log/nginx/${domain}.error.log; location / { try_files \$uri \$uri/ =404; } } EOF echo "[OK] 生成配置文件: ${conf_file}" }这里有个容易踩的坑:heredoc里的\$uri必须转义。因为我们用的是<<EOF,Shell会展开变量,如果不写\$,$uri会被当成Shell变量,结果为空白。写成\$uri,Nginx才能收到正确的$uri变量。这个坑我当年排查了半小时才找到原因,先给你排掉了。
调用方式:
ngx_gen_site_conf "myapp.local" "8080" "/data/www/myapp" "/etc/nginx/conf.d/myapp.conf" ngx_config_check "/usr/sbin/nginx" "/etc/nginx/nginx.conf" ngx_reload "/usr/sbin/nginx" "/etc/nginx/nginx.conf"如果你想做反向代理配置,再封装一个upstream函数:
ngx_gen_proxy_conf() { local domain="$1" local listen_port="$2" local backend_url="$3" local conf_file="$4" cat > "${conf_file}" <<EOF server { listen ${listen_port}; server_name ${domain}; location / { proxy_pass ${backend_url}; proxy_set_header Host \$host; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } } EOF }这就是很多Nginx反向代理配置被脚本化生成的基本套路。函数里可以继续加参数,比如加一个proxy_read_timeout参数,对应热词里提到的"nginx mirror超时时间"等调优场景。
4.2 多端口多站点场景的批量函数
开发环境里经常需要"一键创建N+1个站点的配置",这时候函数之间再互相调用,威力才真正体现出来。我写过这样一个批量生成函数:
ngx_gen_sites_batch() { local base_domain="$1" local start_port="$2" local count="$3" local site_names=("$@") # 从第4个参数开始是站点名列表 local i local port local site_name local conf_dir=/etc/nginx/conf.d for ((i=0; i<count; i++)); do site_name="${site_names[$i]}" port=$((start_port + i)) ngx_gen_site_conf \ "${site_name}.${base_domain}" \ "${port}" \ "/data/www/${site_name}" \ "${conf_dir}/${site_name}.conf" done echo "[OK] 已批量生成 ${count} 个站点配置" }调用:
ngx_gen_sites_batch "dev.local" 8080 3 "app1" "app2" "app3"执行后会自动生成:
- app1.dev.local:8080
- app2.dev.local:8081
- app3.dev.local:8082
为什么要用函数套函数?因为只有把单个站点生成逻辑收敛成一个独立函数(ngx_gen_site_conf),批量函数才能做到"只关心数量和命名规则,不关心配置文件细节"。做设计时一个重要的判断标准是:未来可能独立变化的东西,就要拆成独立函数。
配合自定义域名解析,你需要在开发机的/etc/hosts里加上:
127.0.0.1 app1.dev.local app2.dev.local app3.dev.local这样浏览器里就能直接通过自定义域名访问不同端口的站点,也就是热搜词里提到的"本地+虚拟机 多端口nginx 开发环境多站点自定义域名配置"的完整解法。
4.3 配置备份与回滚函数:预防手滑
每次改Nginx配置之前备份一份,这个习惯能救命。我封装的备份函数:
ngx_backup_conf() { local conf_path="$1" local backup_dir="/data/backup/nginx/$(date +%Y%m%d_%H%M%S)" if [[ ! -f "${conf_path}" ]]; then echo "[ERROR] 配置文件不存在: ${conf_path}" >&2 return 1 fi mkdir -p "${backup_dir}" cp -a "${conf_path}" "${backup_dir}/" echo "[OK] 配置已备份至: ${backup_dir}/$(basename "${conf_path}")" echo "${backup_dir}" # 返回备份目录,方便合并到后续操作 }这里依然沿用了"echo返回数据,return返回状态"的设计。有了备份,再加上前面提到的ngx_config_check,我每次改配置的完整流程是:
ngx_backup_conf /etc/nginx/nginx.conf # 修改配置... ngx_config_check /usr/sbin/nginx /etc/nginx/nginx.conf ngx_reload /usr/sbin/nginx /etc/nginx/nginx.conf如果改坏了,直接恢复备份:
cp /data/backup/nginx/20250314_102030/nginx.conf /etc/nginx/nginx.conf ngx_reload /usr/sbin/nginx /etc/nginx/nginx.conf这套组合拳看着简单,但在生产环境里真的能救命。有时候你改了一个proxy_pass参数、改了一个ssl证书路径(对应热词里"nginx替换ssl证书不生效"这种问题),只要先备份、再检查、再重载,问题定位会快非常多。
5. 常见问题与排查技巧实录
5.1 函数返回值超出255被截断
这是一个经典的Shell坑。return只能返回0~255之间的整数,超出范围会被截断。比如你把某个计数器结果直接return 300,实际上收到的是44(300 - 256)。
对应的排查经验是:如果函数需要返回大整数或字符串,一律用echo输出,用return只表示成功失败。判断是否成功就用if 函数调用; then,需要具体数值就用result=$(函数调用)。两条通道各司其职,能绕开90%的返回值陷阱。
5.2 函数里的变量把全局环境污染了
症状是这样的:脚本A调用了函数B,函数B里临时改了某个变量,执行完之后主流程的变量也跟着变了,导致后续逻辑全乱。
排查思路也不难,用bash -x 脚本.sh跟踪执行,看哪个地方变量值出现了非预期变化。根因基本都是函数内变量没有加local。我的规则很简单:函数内出现的每一个自定义变量,全部local声明,一个都不能漏。这不是洁癖,是保命。
5.3 nginx -t提示OK但reload不生效
这类问题的症状是:nginx -t显示语法没问题,但reload之后新配置没生效。常见原因有:
- 用了
nginx -s reload但reload的是默认路径的nginx.conf,而不是你修改的那个conf文件。比如你改的是/usr/local/nginx/conf/nginx.conf,但reload时用的是/usr/sbin/nginx,两者根本不是同一个实例。 - 修改的server配置是在子配置文件(
conf.d/*.conf)里,但主配置并没有通过include引入这个目录。
验证方式:reload后立刻用curl -I http://域名检查实际响应头,确认是否生效。不要只看命令输出,要看服务端实际行为。这也是我在函数设计里反复强调"把实例相关参数都传入函数"的原因——不同实例用不同路径,不写死,少踩这类坑。
5.4 curl健康检查把代理环境变量也带进去了
有次我在脚本里用curl做健康检查,死活返回Could not resolve proxy,排查半天发现是环境变量里设置了http_proxy,curl默认走了代理,流量被转发到一个根本不通的代理地址。
解决方式是给curl显式关闭代理:
curl --noproxy '*' -s -o /dev/null -w "%{http_code}" "${url}"然后在健康检查函数里统一加上--noproxy '*'参数,避免外部环境影响检测结果。
5.5 函数调试速查表
| 现象 | 可能原因 | 排查命令/手法 |
|---|---|---|
| 函数不接受参数 | 调用时漏写参数,或函数内部用了$0误以为函数名 | 打印FUNCNAME确认函数名,打印$1确认参数 |
| 函数输出多余内容 | echo调试信息混入stdout | 函数内调试信息重定向到>&2 |
| 返回状态总是0 | return后面跟了管道/赋值语句 | return前单独执行判断,或用` |
| 脚本子函数找不到命令 | PATH被函数内覆盖 | 函数内不要轻易重写PATH |
| 配置存在但nginx启动失败 | 配置文件权限、nginx用户权限、端口占用 | 查看/var/log/nginx/error.log,检查端口ss -tlnp |
6. 我在实际使用中的几点体会
写Shell函数的这几年,我最深的一个体会是:函数的边界感就是脚本的工程质量感。每个函数只做一件事,输入输出尽量清晰,变量不裸奔,状态用return传递、数据用echo传递——能做到这几点,脚本基本不会太烂。
另外一个比较主观但很实用的建议:不要一上来就追求把所有逻辑都塞进函数。如果你只是写一个一次性用的临时脚本,20行以内直接写也是OK的。但当脚本开始出现"重复代码超过两处""逻辑层次超过两层"的时候,就值得停下来重构出函数了。判断标准不是代码量,而是重复和维护成本。
最后分享一个小技巧:写函数时尽量把"打印成功信息"和"返回失败状态"结合好。一个函数跑完,调用方既能看到直观的[OK]/[ERROR]输出,又能通过$?拿到精确的状态码,后期不管是接监控告警还是接CI/CD流程,都会顺畅很多。
这套Nginx函数管理脚本我到现在还在用,不同服务器的nginx路径不同,就通过数组配置区分;需要新增站点,就调生成函数批量产出。它解决的不只是"少敲几条命令"的问题,更重要的是把Nginx日常运维变成了一套可预期、可复用、不会删错文件的安全流程。