Shell脚本if/else实战:VPS自动化部署的健壮性设计
1. 项目概述:从零开始写一个真正能用的 Shell 脚本(不是“Hello World”那种)
你刚买了一台 VPS,可能是甲骨文免费的、腾讯云轻量的,也可能是搬瓦工或 Vultr 的——不管哪一家,只要它装的是 Linux(绝大多数都是),你就已经站在了自动化运维的起点上。但问题来了:SSH 登录进去后,面对黑底白字的终端,敲ls、cd、cat这些命令还能应付,可一旦要重复执行 5 个步骤(比如更新系统、安装 Nginx、配置防火墙、下载配置文件、重启服务),再手动敲一遍?三次之后手会抖,五次之后你会怀疑人生。这时候,shell script就不是“可选项”,而是“生存必需品”。
我带过几十个刚接触服务器的新手,90% 的人卡在 Part 3——不是不会写echo "hello",而是不知道怎么让脚本“看情况做事”。比如:“如果 nginx 没装,就自动装;如果已装,就跳过”;“如果配置文件存在,先备份再覆盖;如果不存在,直接新建”;“如果 curl 下载失败,别硬着头皮往下跑,停下来报错并提示我”。这些逻辑,全靠if、else、test、[ ]这几个看似简单、实则极易出错的结构撑起来。网上很多教程教你怎么写if [ $a = "b" ]; then,但没人告诉你:为什么等号两边必须有空格?为什么字符串比较要用双引号包裹变量?为什么[[ ]]比[ ]更安全?为什么bash -n script.sh是上线前必做的一步?这些细节,恰恰是脚本从“能跑”升级到“稳跑”的分水岭。
这篇内容,就是专为卡在 Part 3 的你写的。它不讲 bash 基础语法(那些你早该在 Part 1/2 学完),也不堆砌冷门参数,而是聚焦真实 VPS 场景下的if/else 实战闭环:从判断命令是否存在、检测端口是否被占用、校验用户输入合法性,到处理网络请求返回码、解析 JSON 响应、做多级条件嵌套。所有示例都基于你明天就能登录自己 VPS 复现的场景——比如用curl -fssl https://example.com/install.sh | bash这类一键安装命令时,背后脚本到底怎么判断环境是否就绪;比如为什么bash: line 778: openclaw-cn: command not found这种报错一出现,整个流程就崩了,而一个健壮的 if 判断本可以提前拦截。适合所有正在用 VPS 搭建网站、部署工具、跑定时任务,或者正被各种curl | bash安装脚本支配的新手和中级用户。
2. 核心设计思路:为什么 if/else 不是“加个判断那么简单”
2.1 真实 VPS 场景下的三大判断刚需
在本地写个脚本,测试通过就完事了;但在 VPS 上,环境千差万别:系统可能是 Ubuntu 22.04 或 CentOS 7,包管理器是 apt 还是 yum,关键命令路径可能被自定义修改,甚至同一台机器上午能连通 GitHub,下午因网络波动就超时。这就决定了 VPS 上的 if/else 必须解决三个底层问题:
命令可用性判断:不能假设
curl一定存在(有些最小化镜像默认不装),也不能假设jq已预装(解析 API 返回必备)。if command -v curl >/dev/null 2>&1; then ...是比which curl更可靠的方式,因为command -v是 POSIX 标准,不依赖外部命令,且输出纯净(which在某些 shell 下行为不一致)。状态码与返回值的精准捕获:
curl的-f参数只管 HTTP 状态码 4xx/5xx,但网络超时、DNS 解析失败、SSL 证书错误都会让curl返回非零退出码(通常是 6、7、28、60)。如果脚本只检查if curl ...; then,这些错误会被当成“成功”继续执行,后续操作必然失败。正确做法是显式捕获$?并分支处理,或用set -e配合||fallback。变量与输入的防御性校验:VPS 脚本常需用户传参(如
./install.sh --port 8080),但用户可能输错、漏输、输成字母。if [ -z "$PORT" ] || ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then这一长串判断,缺一不可。少一个[ -z "$PORT" ],空输入会导致[[ "" =~ ... ]]报错;少一个范围检查,用户输个 70000,iptables直接拒绝生效。
提示:很多新手用
if [ $VAR == "value" ],这是高危写法。当$VAR为空时,实际执行的是if [ == "value" ],bash 会报错[: ==: unary operator expected。永远用if [ "$VAR" == "value" ],双引号是保命符。
2.2 为什么[[ ]]是 VPS 脚本的默认选择
几乎所有主流 VPS 发行版(Ubuntu、Debian、CentOS Stream)默认 shell 是 bash,而非 POSIX sh。这意味着你可以放心使用[[ ]]替代[ ],它带来三重实质性提升:
- 模式匹配原生支持:
[[ $URL =~ ^https?:// ]]可直接用正则判断协议,而[ ]需调用expr或case,代码臃肿且易错。 - 避免单词拆分陷阱:
[ "$PATH" = "/usr/bin:/bin" ]安全,但[ $PATH = "/usr/bin:/bin" ]在$PATH含空格时会崩溃;[[ $PATH = "/usr/bin:/bin" ]]即使不加引号也安全,因为[[ ]]内部自动处理。 - 逻辑运算更直观:
[[ $A == "x" && $B =~ ^[0-9]+$ ]]比[ "$A" = "x" ] && [ "$B" ] && [[ "$B" =~ ^[0-9]+$ ]]少写一半代码,且短路求值行为更符合直觉。
但注意:如果你的脚本明确要求兼容 dash/sh(如 Debian 的/bin/sh),就必须退回[ ]。不过对 VPS 场景,我们默认目标是 bash,所以全文采用[[ ]]语法。
2.3 “一行式” vs “块结构”:何时该用哪种写法
网上常见curl ... | bash这种写法,其背后的 install.sh 往往是高度压缩的单行逻辑。但作为学习者,必须分清两种场景:
初始化脚本(如一键安装):追求极简、防中断,适合用
&&/||链式判断。例如:command -v curl >/dev/null 2>&1 || { echo "curl not found. Installing..."; apt update && apt install -y curl; }这里
||后是{ }包裹的命令组,确保 curl 缺失时整组执行,而不是只执行apt update。功能型脚本(如部署服务):需要清晰的错误上下文、日志记录、用户提示,必须用
if/elif/else块结构。例如检测端口占用:if ss -tuln | grep -q ":$PORT "; then echo "Port $PORT is occupied. Please choose another." exit 1 else echo "Port $PORT is free. Proceeding..." fi块结构便于插入
echo "[INFO]..."日志、logger系统日志记录,以及后续扩展elif分支(如“若端口被 nginx 占用,则询问是否停掉 nginx”)。
我的经验是:所有超过 3 行的判断逻辑,一律用块结构;所有初始化检查(命令、权限、基础依赖),可用链式简化,但必须加注释说明意图。
3. 核心细节解析:VPS 脚本中 if/else 的 7 个致命细节
3.1 空格:bash 中最沉默的杀手
if [ $VAR == "val" ]; then和if [ "$VAR" == "val" ]; then看似只差两对引号,实则天壤之别。我们用一个 VPS 典型场景验证:
# 场景:读取用户输入的域名,判断是否为空 read -p "Enter domain (e.g., example.com): " DOMAIN if [ $DOMAIN == "" ]; then echo "Domain is empty!" fi当用户直接回车(DOMAIN为空)时,实际执行的是if [ == "" ]; then,bash 报错:[: ==: unary operator expected。原因:[ ]是test命令的同义词,它要求第一个参数是操作符(如-z,==),但空变量导致==成了第一个参数,语法非法。
正确解法:
if [[ -z "$DOMAIN" ]]; then # 推荐:用 [[ ]] + -z echo "Domain is empty!" fi # 或 if [ -z "$DOMAIN" ]; then # 兼容 sh:用 [ ] + -z,且变量必须引号 echo "Domain is empty!" fi注意:
-z判断字符串长度为 0,比== ""更语义化,且不受空格影响。这是 VPS 脚本中最该养成的习惯——所有变量参与[ ]或[[ ]]判断时,无条件加双引号。
3.2 字符串比较:==、=、-eq的血泪区别
新手常混淆这三者,导致脚本在数字比较时静默失败:
==和=在[[ ]]中等价,用于字符串比较([[ "$A" == "$B" ]])。=在[ ]中是标准 POSIX 写法([ "$A" = "$B" ]),==在[ ]中是 bash 扩展,不跨 shell 兼容。-eq是仅用于整数比较的操作符([ "$NUM" -eq 42 ]),若$NUM是"42 "(带空格)或"abc",-eq会报错integer expression expected。
VPS 实战案例:校验用户输入的端口号
read -p "Enter port (1-65535): " PORT # ❌ 错误:用 == 比较数字,且未引号 if [ $PORT == 8080 ]; then echo "Using default port 8080" fi # ✅ 正确:先用 [[ ]] 判断是否为纯数字,再用 -eq 比较 if [[ "$PORT" =~ ^[0-9]+$ ]] && [ "$PORT" -ge 1 ] && [ "$PORT" -le 65535 ]; then echo "Port $PORT is valid." else echo "Invalid port: must be number between 1 and 65535" exit 1 fi这里用了[[ ]]的正则匹配^[0-9]+$确保$PORT是纯数字字符串,再用[ ]的-ge/-le做数值范围判断。两步缺一不可:正则防非数字输入,数值比较防越界。
3.3 文件与目录判断:-f、-d、-e的真实含义
VPS 脚本大量涉及文件操作(如检查配置文件是否存在、创建日志目录)。这三个测试操作符常被误用:
-e FILE:文件或目录存在(existence),但不区分类型。-f FILE:FILE 是普通文件(regular file),排除目录、符号链接、设备文件。-d FILE:FILE 是目录(directory)。
典型错误场景:脚本想“如果 nginx 配置目录不存在,就创建它”,却写了:
if [ ! -f /etc/nginx/conf.d ]; then # ❌ 用 -f 判断目录! mkdir -p /etc/nginx/conf.d fi结果:/etc/nginx/conf.d是目录,-f返回 false,! -f为 true,脚本误判为“不存在”而反复创建,但mkdir -p无害。问题在于逻辑混乱——你本意是判断“目录是否存在”,该用-d。
正确写法:
if [[ ! -d /etc/nginx/conf.d ]]; then echo "Creating nginx config directory..." mkdir -p /etc/nginx/conf.d chown -R www-data:www-data /etc/nginx/conf.d fi进阶技巧:用
-L判断符号链接,-S判断 socket 文件(如/var/run/docker.sock),-r/-w/-x判断读写执行权限。VPS 上部署服务时,if [[ ! -r "$CONFIG_FILE" ]]; then echo "Config not readable! Check permissions."; exit 1; fi是必备检查。
3.4 命令执行结果判断:$?、&&、||的协作艺术
curl下载失败是 VPS 脚本最高频故障。很多人写:
curl -fssl https://example.com/app.tar.gz -o /tmp/app.tar.gz if [ $? -eq 0 ]; then tar -xzf /tmp/app.tar.gz -C /opt/ fi逻辑没错,但冗余。bash 提供更简洁的&&:
curl -fssl https://example.com/app.tar.gz -o /tmp/app.tar.gz && \ tar -xzf /tmp/app.tar.gz -C /opt/&&的含义是:仅当前面命令退出码为 0(成功)时,才执行后面命令。这比显式检查$?更符合 shell 习惯。
但&&无法处理“失败时的差异化响应”。此时||出场:
curl -fssl https://example.com/app.tar.gz -o /tmp/app.tar.gz || { echo "Download failed! Retrying in 5s..." sleep 5 curl -fssl https://example.com/app.tar.gz -o /tmp/app.tar.gz || { echo "Download failed twice. Aborting." exit 1 } }这里用{ }包裹多条命令,实现“失败→等待→重试→再失败→退出”的完整流程。
关键原则:单步依赖用
&&,多步容错用if/else,复杂恢复逻辑用|| { }。不要在一个脚本里混用三种风格,保持可读性。
3.5 网络与服务状态判断:nc、ss、systemctl的组合拳
VPS 上判断服务是否就绪,不能只看进程是否存在(ps aux | grep nginx易误判),而要看端口监听和服务状态:
- 端口监听:
ss -tuln | grep -q ":80 "(-tTCP,-uUDP,-llistening,-nnumeric),比netstat更快更轻量,是现代 VPS 首选。 - 服务状态:
systemctl is-active --quiet nginx(--quiet无输出,仅靠退出码判断),比systemctl status nginx | grep "active (running)"更可靠。 - 网络连通:
nc -zv example.com 443(-zscan,-vverbose),比ping更精准(ping可能被禁,但端口开放才代表服务可达)。
实战整合:部署前的全栈健康检查
check_prerequisites() { local issues=() # 检查 curl 是否存在 if ! command -v curl >/dev/null 2>&1; then issues+=("curl not found. Run: apt install -y curl") fi # 检查 80 端口是否空闲 if ss -tuln | grep -q ":80 "; then issues+=("Port 80 is occupied. Stop conflicting service first.") fi # 检查能否访问 GitHub(关键 CDN) if ! nc -z github.com 443 2>/dev/null; then issues+=("Cannot reach github.com:443. Check network/firewall.") fi # 汇总报错 if [[ ${#issues[@]} -gt 0 ]]; then echo "Prerequisites check FAILED:" printf " - %s\n" "${issues[@]}" echo "Fix above issues and re-run." exit 1 else echo "All prerequisites OK." fi }这个函数将多个if判断结果收集到数组,最后统一输出,比每个if单独exit更友好。
3.6 用户交互与输入处理:read的安全用法
VPS 脚本常需用户确认(如“是否继续安装?”)或输入参数(如数据库密码)。read的默认行为极不安全:
read VAR:遇到空格、制表符会截断,输入my db只存my。read -p "Input: " VAR:提示符后输入,但依然会截断。read -r VAR:-r禁用反斜杠转义,防止输入\n被解释为换行。
安全模板:
# 读取密码(不回显) read -s -p "Enter password: " PASSWORD echo # 换行 # 读取任意字符串(保留空格和特殊字符) read -r -p "Enter full domain (e.g., www.example.com): " DOMAIN # 校验非空 if [[ -z "$PASSWORD" ]] || [[ -z "$DOMAIN" ]]; then echo "Error: Password and domain cannot be empty." exit 1 fi提示:密码明文存储在变量中不安全,生产环境应改用
openssl rand -base64 12生成,或用mktemp创建临时文件。但对学习脚本,read -s是最直接的方案。
3.7 错误处理与调试:set -e、set -u、set -o pipefail的铁三角
一个健壮的 VPS 脚本,必须在开头启用三重防护:
set -e:任何命令失败(退出码非 0)立即退出脚本。避免apt update失败后,脚本仍执行apt install nginx。set -u:引用未声明变量时报错。防止if [ "$USER_NAME" == "admin" ]因$USER_NAME未赋值而变成[ == "admin" ]。set -o pipefail:管道中任一命令失败,整个管道返回失败码。例如curl ... | jq ...,若curl失败但jq因输入为空而成功,set -o pipefail会让整个管道返回非 0,触发set -e退出。
标准脚本头:
#!/bin/bash set -euo pipefail # -e: exit on any error # -u: exit on undefined variable # -o pipefail: exit if any command in pipeline fails # 全局变量 SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_FILE="$SCRIPT_DIR/install.log" exec > >(tee -a "$LOG_FILE") 2>&1 # 同时输出到屏幕和日志加上exec > >(tee ...),所有echo输出自动记录到日志,排错时直接tail -f install.log,效率翻倍。
4. 实操过程:手把手写一个 VPS 通用部署脚本(含完整 if/else)
4.1 需求定义:我们要做什么?
目标:写一个deploy-web.sh脚本,运行在任意 Ubuntu/Debian VPS 上,完成以下任务:
- 检查 root 权限(
if [[ $EUID -ne 0 ]]; then ...) - 检查系统版本(Ubuntu 20.04+ 或 Debian 11+)
- 更新 apt 缓存,安装必要工具(curl、jq、nginx)
- 创建网站目录
/var/www/myapp,设置权限 - 下载一个示例 HTML 文件(模拟应用部署)
- 配置 Nginx 反向代理(监听 80 端口,代理到本地 3000)
- 启动并启用 Nginx
所有步骤均加入 if/else 判断,失败时给出明确提示并退出。
4.2 脚本骨架与权限检查
#!/bin/bash set -euo pipefail # ====== 1. 权限检查 ====== if [[ $EUID -ne 0 ]]; then echo "Error: This script must be run as root." echo "Try: sudo $0" exit 1 fi echo "[INFO] Running as root. Proceeding..." # ====== 2. 系统信息获取 ====== OS_NAME=$(grep -oP '^(ID=)?\K\w+' /etc/os-release) OS_VERSION=$(grep -oP 'VERSION_ID="?\K[^"]+' /etc/os-release | cut -d. -f1) # ====== 3. 系统兼容性判断 ====== if [[ "$OS_NAME" == "ubuntu" ]]; then if [[ "$OS_VERSION" -lt 20 ]]; then echo "Error: Ubuntu $OS_VERSION not supported. Minimum: Ubuntu 20.04." exit 1 fi elif [[ "$OS_NAME" == "debian" ]]; then if [[ "$OS_VERSION" -lt 11 ]]; then echo "Error: Debian $OS_VERSION not supported. Minimum: Debian 11 (bullseye)." exit 1 fi else echo "Error: Unsupported OS '$OS_NAME'. Only Ubuntu/Debian supported." exit 1 fi echo "[INFO] Detected $OS_NAME $OS_VERSION. Compatible." # ====== 4. 依赖检查与安装 ====== DEPS=("curl" "jq" "nginx") for dep in "${DEPS[@]}"; do if ! command -v "$dep" >/dev/null 2>&1; then echo "[INFO] Installing $dep..." apt update -qq && apt install -y "$dep" >/dev/null 2>&1 else echo "[INFO] $dep is already installed." fi done这段代码展示了:
[[ $EUID -ne 0 ]]判断 root 权限($EUID是有效用户 ID,比id -u更可靠)grep -oP用 Perl 正则精准提取/etc/os-release中的 ID 和 VERSION_IDfor循环遍历依赖数组,用command -v检查每个命令,缺失则apt installapt update -qq的-qq参数减少输出噪音,>/dev/null 2>&1重定向所有输出
4.3 文件与服务操作:带容错的完整流程
# ====== 5. 创建网站目录 ====== WEB_ROOT="/var/www/myapp" if [[ ! -d "$WEB_ROOT" ]]; then echo "[INFO] Creating web root: $WEB_ROOT" mkdir -p "$WEB_ROOT" chown -R $USER:$USER "$WEB_ROOT" # 改为当前用户,方便后续上传 else echo "[INFO] Web root $WEB_ROOT already exists." fi # ====== 6. 下载示例页面 ====== INDEX_URL="https://raw.githubusercontent.com/yourname/myapp/main/index.html" INDEX_PATH="$WEB_ROOT/index.html" if [[ ! -f "$INDEX_PATH" ]]; then echo "[INFO] Downloading index.html from $INDEX_URL" if curl -fssl -o "$INDEX_PATH" "$INDEX_URL" 2>/dev/null; then echo "[INFO] index.html downloaded successfully." chmod 644 "$INDEX_PATH" else echo "Error: Failed to download index.html from $INDEX_URL" echo "Please check URL or network connectivity." exit 1 fi else echo "[INFO] index.html already exists. Skipping download." fi # ====== 7. Nginx 配置 ====== NGINX_CONF="/etc/nginx/sites-available/myapp" NGINX_LINK="/etc/nginx/sites-enabled/myapp" # 生成配置内容(用 cat <<'EOF' 保留变量原样) cat > "$NGINX_CONF" <<'EOF' server { listen 80; server_name _; location / { root /var/www/myapp; index index.html; } } EOF # 创建软链接并测试配置 if [[ ! -L "$NGINX_LINK" ]]; then ln -sf "$NGINX_CONF" "$NGINX_LINK" echo "[INFO] Nginx site enabled." else echo "[INFO] Nginx site already enabled." fi # 测试 Nginx 配置语法 if nginx -t >/dev/null 2>&1; then echo "[INFO] Nginx configuration syntax OK." else echo "Error: Nginx configuration test failed!" echo "Run 'nginx -t' manually for details." exit 1 fi # ====== 8. 启动 Nginx ====== if systemctl is-active --quiet nginx; then echo "[INFO] Nginx is already running. Reloading config..." systemctl reload nginx else echo "[INFO] Starting Nginx..." systemctl start nginx systemctl enable nginx fi echo "" echo "🎉 Deployment completed!" echo "Your app is live at http://$(hostname -I | awk '{print $1}')" echo "Configuration: $NGINX_CONF"关键点解析:
cat > file <<'EOF'中的'EOF'(单引号)阻止 shell 变量展开,确保配置文件中的$符号原样写入。systemctl is-active --quiet nginx用--quiet避免输出干扰,仅靠退出码判断。hostname -I | awk '{print $1}'获取主 IP,比ip a更简洁,适配多数 VPS。
4.4 调试与日志:让脚本“会说话”
在脚本开头加入:
# 启用调试模式(可选) # set -x # 取消注释此行可看到每条命令执行过程 # 日志记录 LOG_FILE="/var/log/deploy-web.log" exec > >(tee -a "$LOG_FILE") 2>&1 echo "=== $(date) ===" echo "Script started by: $(whoami)" echo "System: $(uname -a)"运行时加sudo bash -x ./deploy-web.sh,-x会打印每条执行的命令(如+ mkdir -p /var/www/myapp),配合日志,排错效率极高。
5. 常见问题与排查技巧实录:VPS 脚本的 12 个经典坑
5.1 问题速查表
| 问题现象 | 可能原因 | 快速诊断命令 | 解决方案 |
|---|---|---|---|
bash: line 778: openclaw-cn: command not found | 脚本中调用了未安装的命令openclaw-cn,且未用if command -v检查 | command -v openclaw-cn | 在调用前添加if ! command -v openclaw-cn >/dev/null; then echo "Install openclaw-cn first"; exit 1; fi |
./script.sh: line 12: [: missing \]'` | [ ]内部缺少空格,如[ "$VAR"=="val" ] | bash -n script.sh(语法检查) | 确保[后、==前后、]前均有空格;变量加引号 |
curl: (60) SSL certificate problem | VPS 系统时间错误或 CA 证书过期 | date; curl -v https://google.com | apt install -y ca-certificates && update-ca-certificates,或ntpdate -s time.nist.gov |
Permission denied (publickey)when SSH | SSH 密钥权限太宽松 | ls -l ~/.ssh/id_rsa | chmod 600 ~/.ssh/id_rsa; chmod 700 ~/.ssh |
bash must not run in posix mode. please unset posixly_correct... | 环境变量POSIXLY_CORRECT被设为非空 | echo $POSIXLY_CORRECT | unset POSIXLY_CORRECT,或在脚本开头加unset POSIXLY_CORRECT |
No such file or directoryon shebang line | 脚本在 Windows 编辑器保存,含 CRLF 换行符 | file script.sh | dos2unix script.sh或在 VS Code 中切换行尾序列(LF) |
5.2 独家避坑技巧
技巧 1:用bash -n做上线前“CT扫描”
每次修改脚本后,运行bash -n script.sh。它只做语法检查,不执行任何命令,能瞬间发现:
if没有fi[缺少][[缺少]]- 变量名拼写错误(
$USER_NAM→$USER_NAME)
这是比./script.sh直接运行更安全的第一道防线。
技巧 2:[[ ]]中的正则必须用=~,且右侧不加引号
# ❌ 错误:右侧加引号,正则失效 if [[ "$URL" =~ "^https?://" ]]; then ... # ✅ 正确:右侧不加引号,^https?:// 是正则模式 if [[ "$URL" =~ ^https?:// ]]; then ...因为=~右侧是模式,不是字符串。加引号会把它当字面量匹配。
技巧 3:处理curl的多种失败码
curl -fssl https://api.example.com/data.json -o data.json case $? in 0) echo "Success" ;; 6) echo "Could not resolve host" ;; 7) echo "Failed to connect" ;; 28) echo "Connection timeout" ;; 60) echo "SSL certificate error" ;; *) echo "Other curl error: $?" ;; esaccase比if/elif更适合处理多分支退出码。
技巧 4:read输入时按 Ctrl+C 的优雅退出
默认read被 Ctrl+C 中断会报错Interrupted system call。加-t 30限制 30 秒超时,并捕获SIGINT:
trap 'echo -e "\nInstallation cancelled by user."; exit 1' SIGINT read -t 30 -p "Continue? (y/N): " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "Proceeding..." else echo "Aborted." exit 0 fi技巧 5:用diff检查配置文件是否变更
部署时,常需“如果配置文件被修改过,就备份旧版”。用diff比md5sum更直观:
if [[ -f /etc/nginx/nginx.conf ]] && ! diff -q /etc/nginx/nginx.conf /tmp/nginx.conf.bak >/dev/null; then cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.$(date +%s) echo "Backup created: /etc/nginx/nginx.conf.$(date +%s)" fi5.3 真实排错现场:一次curl | bash失败的完整复盘
用户报错:curl -fssl https://openclaw.ai/install.sh | bash执行到一半报bash: line 778: openclaw-cn: command not found,然后退出。
我的排查步骤:
重现问题:
curl -fssl https://openclaw.ai/install.sh | head -n 780 | tail -n 10—— 查看第 778 行附近代码
输出:openclaw-cn --version || { echo "Installing openclaw-cn..."; pip3 install openclaw-cn; }定位根源:
openclaw-cn是 Python 包,但脚本未检查pip3是否存在,也未检查python3版本(openclaw-cn要求 Python 3.8+)。补丁方案(在调用前插入):
# Check Python and pip if ! command -v python3 >/dev/null 2>&1; then echo "Error: python3 not found. Install Python 3.8+ first." exit 1 fi if [[ $(python3 --version | cut -d' ' -f2 | cut -d. -f1) -
