强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Bash 脚本编写教程 / 17 - 网络脚本

17 - 网络脚本

17.1 curl —— HTTP 客户端

基本用法

# GET 请求
curl "https://api.example.com/users"

# 保存到文件
curl -o output.html "https://example.com"
curl -O "https://example.com/file.tar.gz"  # 使用远程文件名

# 静默模式(不显示进度)
curl -s "https://api.example.com/users"

# 显示 HTTP 头
curl -I "https://example.com"

# 详细调试
curl -v "https://example.com"

# 跟随重定向
curl -L "https://example.com/redirect"

# 超时设置
curl --connect-timeout 5 --max-time 30 "https://example.com"

# 忽略 SSL 证书验证
curl -k "https://self-signed.example.com"

# 自定义请求头
curl -H "Authorization: Bearer token123" \
     -H "Content-Type: application/json" \
     "https://api.example.com/users"

POST/PUT/DELETE 请求

# POST JSON 数据
curl -X POST "https://api.example.com/users" \
     -H "Content-Type: application/json" \
     -d '{"name":"张三","email":"[email protected]"}'

# POST 表单数据
curl -X POST "https://api.example.com/login" \
     -d "username=admin&password=secret"

# 上传文件
curl -X POST "https://api.example.com/upload" \
     -F "file=@/path/to/file.pdf" \
     -F "description=测试文件"

# PUT 更新
curl -X PUT "https://api.example.com/users/1" \
     -H "Content-Type: application/json" \
     -d '{"name":"张三(已更新)"}'

# DELETE
curl -X DELETE "https://api.example.com/users/1"

# 使用文件作为请求体
curl -X POST "https://api.example.com/data" \
     -H "Content-Type: application/json" \
     -d @request.json

实用技巧

# 检查 HTTP 状态码
status_code=$(curl -s -o /dev/null -w "%{http_code}" "https://example.com")
if [[ "$status_code" == "200" ]]; then
    echo "请求成功"
fi

# 获取响应时间
curl -s -o /dev/null -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTotal: %{time_total}s\n" \
     "https://example.com"

# 下载带进度条
curl -# -O "https://example.com/largefile.tar.gz"

# 带认证
curl -u "username:password" "https://api.example.com/admin"

# 使用 .netrc 文件
curl --netrc "https://api.example.com"

# 并发请求(curl 7.66+)
curl --parallel --parallel-max 5 \
     "https://api.example.com/users/1" \
     "https://api.example.com/users/2" \
     "https://api.example.com/users/3"

# 重试
curl --retry 3 --retry-delay 5 "https://api.example.com/data"

17.2 wget —— 下载工具

# 基本下载
wget "https://example.com/file.tar.gz"

# 指定输出文件名
wget -O output.tar.gz "https://example.com/file.tar.gz"

# 后台下载
wget -b "https://example.com/largefile.tar.gz"
tail -f wget-log  # 查看进度

# 断点续传
wget -c "https://example.com/largefile.tar.gz"

# 递归下载整个网站
wget -r -l 2 -np "https://example.com/docs/"

# 限制下载速度
wget --limit-rate=1m "https://example.com/file.tar.gz"

# 批量下载(从文件读取 URL)
wget -i urls.txt

# 镜像网站
wget --mirror --convert-links --page-requisites "https://example.com"

# 静默模式
wget -q "https://example.com/file.tar.gz"

# 超时设置
wget --timeout=30 --tries=3 "https://example.com/file.tar.gz"

17.3 SSH 远程执行

# 基本远程命令
ssh user@host "uname -a"

# 多条命令
ssh user@host "cd /opt && ls -la && df -h"

# 执行本地脚本
ssh user@host 'bash -s' < local_script.sh

# 带参数的远程脚本
ssh user@host 'bash -s' -- arg1 arg2 < script.sh

# 使用 SSH 密钥
ssh -i ~/.ssh/mykey user@host "command"

# 非交互模式(禁用密码提示)
ssh -o BatchMode=yes -o ConnectTimeout=5 user@host "command"

# SSH 端口转发
ssh -L 8080:localhost:80 user@host          # 本地转发
ssh -R 9090:localhost:8080 user@host        # 远程转发
ssh -D 1080 user@host                       # 动态代理(SOCKS)

# SCP 文件传输
scp file.txt user@host:/remote/path/        # 上传
scp user@host:/remote/file.txt ./            # 下载
scp -r ./dir user@host:/remote/path/        # 递归

# rsync 同步
rsync -avz --progress ./src/ user@host:/remote/src/
rsync -avz --delete ./src/ user@host:/remote/src/  # 镜像(删除多余文件)

SSH 配置(~/.ssh/config)

# ~/.ssh/config
Host prod-server
    HostName 192.168.1.100
    User admin
    Port 2222
    IdentityFile ~/.ssh/prod_key
    StrictHostKeyChecking yes

Host dev-*
    User developer
    IdentityFile ~/.ssh/dev_key

Host dev-web
    HostName 192.168.1.201

Host dev-db
    HostName 192.168.1.202
# 使用配置
ssh prod-server        # 等价于 ssh -p 2222 -i ~/.ssh/prod_key [email protected]
ssh dev-web
ssh dev-db

17.4 网络诊断

# Ping 检查
ping -c 3 -W 2 "8.8.8.8"

# 端口检查(多种方法)
# 方法一:nc (netcat)
nc -zv "example.com" 80
nc -zvw 3 "example.com" 443

# 方法二:bash 内置
timeout 3 bash -c "echo >/dev/tcp/8.8.8.8/53" 2>/dev/null && echo "端口开放" || echo "端口关闭"

# 方法三:nmap
nmap -p 80,443 example.com

# DNS 查询
nslookup example.com
dig example.com
dig +short example.com

# 路由追踪
traceroute example.com

# 网络接口
ip addr show
ip route show

# HTTP 探测
curl -sf -o /dev/null -w "%{http_code}" "https://example.com"

# 检查 SSL 证书
echo | openssl s_client -connect example.com:443 2>/dev/null | \
    openssl x509 -noout -dates

# 带宽测试
curl -o /dev/null -w "下载速度: %{speed_download} bytes/sec\n" \
     "http://speedtest.tele2.net/1MB.zip"

17.5 业务场景:自动化部署脚本

#!/bin/bash
# deploy.sh —— SSH 自动化部署脚本
set -euo pipefail

readonly APP_NAME="myapp"
readonly REMOTE_HOST="deploy@prod-server"
readonly REMOTE_DIR="/opt/$APP_NAME"
readonly LOCAL_ARTIFACT="./dist/$APP_NAME.tar.gz"
readonly SSH_KEY="~/.ssh/deploy_key"
readonly SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=10 -i $SSH_KEY"

log() { echo "[$(date '+%H:%M:%S')] $*"; }

# 前置检查
pre_check() {
    log "执行前置检查..."
    
    [[ -f "$LOCAL_ARTIFACT" ]] || { log "制品不存在: $LOCAL_ARTIFACT"; exit 1; }
    
    # 检查 SSH 连通性
    if ! ssh $SSH_OPTS "$REMOTE_HOST" "echo ok" &>/dev/null; then
        log "SSH 连接失败"
        exit 1
    fi
    
    # 检查磁盘空间
    local available
    available=$(ssh $SSH_OPTS "$REMOTE_HOST" "df -BG $REMOTE_DIR | awk 'NR==2 {gsub(/G/,\"\"); print \$4}'")
    if ((available < 2)); then
        log "磁盘空间不足: ${available}G"
        exit 1
    fi
    
    log "前置检查通过"
}

# 备份当前版本
backup_current() {
    log "备份当前版本..."
    local backup_name="${APP_NAME}_backup_$(date +%Y%m%d_%H%M%S)"
    ssh $SSH_OPTS "$REMOTE_HOST" "
        if [[ -d $REMOTE_DIR/current ]]; then
            cp -r $REMOTE_DIR/current $REMOTE_DIR/backups/$backup_name
            # 只保留最近5个备份
            ls -dt $REMOTE_DIR/backups/* | tail -n +6 | xargs rm -rf 2>/dev/null
        fi
    "
    log "备份完成: $backup_name"
}

# 上传制品
upload_artifact() {
    log "上传制品..."
    scp $SSH_OPTS "$LOCAL_ARTIFACT" "$REMOTE_HOST:$REMOTE_DIR/releases/"
    log "上传完成"
}

# 部署
deploy() {
    log "部署新版本..."
    ssh $SSH_OPTS "$REMOTE_HOST" bash << 'REMOTE_SCRIPT'
        set -euo pipefail
        cd /opt/myapp/releases
        
        # 解压最新制品
        latest=$(ls -t *.tar.gz | head -1)
        mkdir -p new_version
        tar xzf "$latest" -C new_version
        
        # 切换符号链接
        ln -sfn /opt/myapp/releases/new_version /opt/myapp/current
        
        # 重启服务
        sudo systemctl restart myapp
        
        # 等待服务就绪
        for i in {1..30}; do
            if curl -sf http://localhost:8080/health &>/dev/null; then
                echo "服务就绪"
                exit 0
            fi
            sleep 1
        done
        echo "服务启动超时"
        exit 1
REMOTE_SCRIPT
    log "部署完成"
}

# 健康检查
health_check() {
    log "执行远程健康检查..."
    local result
    result=$(ssh $SSH_OPTS "$REMOTE_HOST" "
        status=\$(curl -sf http://localhost:8080/health 2>/dev/null)
        if [[ -n \"\$status\" ]]; then
            echo \"OK: \$status\"
        else
            echo 'FAIL'
        fi
    ")
    
    if [[ "$result" == OK* ]]; then
        log "✅ 健康检查通过: $result"
    else
        log "❌ 健康检查失败"
        return 1
    fi
}

# 回滚
rollback() {
    log "执行回滚..."
    ssh $SSH_OPTS "$REMOTE_HOST" bash << 'REMOTE_SCRIPT'
        set -euo pipefail
        latest_backup=$(ls -dt /opt/myapp/backups/* | head -1)
        if [[ -z "$latest_backup" ]]; then
            echo "没有可用备份"
            exit 1
        fi
        ln -sfn "$latest_backup" /opt/myapp/current
        sudo systemctl restart myapp
        echo "已回滚到: $(basename "$latest_backup")"
REMOTE_SCRIPT
    log "回滚完成"
}

# 主流程
main() {
    local action="${1:-deploy}"
    
    case "$action" in
        deploy)
            pre_check
            backup_current
            upload_artifact
            deploy
            health_check
            log "🎉 部署成功!"
            ;;
        rollback)
            rollback
            health_check
            log "🔄 回滚完成!"
            ;;
        status)
            health_check
            ;;
        *)
            echo "用法: $0 {deploy|rollback|status}"
            exit 1
            ;;
    esac
}

main "$@"

17.6 业务场景:API 健康监控

#!/bin/bash
# api_monitor.sh —— API 端点健康监控
set -euo pipefail

readonly ALERT_EMAIL="[email protected]"
readonly CHECK_INTERVAL=60
readonly MAX_FAILURES=3

declare -A ENDPOINTS=(
    ["主站"]="https://example.com"
    ["API"]="https://api.example.com/health"
    ["数据库"]="https://api.example.com/db-health"
    ["缓存"]="https://api.example.com/cache-health"
)

declare -A FAIL_COUNT

check_endpoint() {
    local name="$1"
    local url="$2"
    local timeout="${3:-10}"
    
    local start_time status_code response_time
    start_time=$(date +%s%N)
    
    status_code=$(curl -s -o /dev/null -w "%{http_code}" \
        --connect-timeout "$timeout" \
        --max-time "$timeout" \
        "$url" 2>/dev/null || echo "000")
    
    local end_time
    end_time=$(date +%s%N)
    response_time=$(( (end_time - start_time) / 1000000 ))
    
    if [[ "$status_code" == "200" ]]; then
        echo "  ✅ $name: HTTP $status_code (${response_time}ms)"
        FAIL_COUNT["$name"]=0
        return 0
    else
        FAIL_COUNT["$name"]=$(( ${FAIL_COUNT["$name"]:-0} + 1 ))
        echo "  🔴 $name: HTTP $status_code (${response_time}ms) [失败${FAIL_COUNT[$name]}次]"
        
        if [[ ${FAIL_COUNT["$name"]} -ge $MAX_FAILURES ]]; then
            send_alert "$name" "$url" "$status_code"
        fi
        return 1
    fi
}

send_alert() {
    local name="$1"
    local url="$2"
    local status="$3"
    
    echo "API 监控告警: $name ($url) 状态码: $status" | \
        mail -s "[告警] API 健康检查失败" "$ALERT_EMAIL" 2>/dev/null || true
}

main() {
    echo "========================================"
    echo "  API 健康监控 - $(date '+%Y-%m-%d %H:%M:%S')"
    echo "========================================"
    
    local failed=0
    
    for name in "${!ENDPOINTS[@]}"; do
        check_endpoint "$name" "${ENDPOINTS[$name]}" || ((failed++))
    done
    
    echo ""
    if [[ $failed -gt 0 ]]; then
        echo "⚠️  $failed 个端点异常"
    else
        echo "✅ 所有端点正常"
    fi
}

main

17.7 注意事项

陷阱说明解决方案
curl 超时未设置脚本挂起始终设置 --connect-timeout--max-time
SSH 密码交互cron 中无法输入密码使用 SSH 密钥
SSH 公钥未确认首次连接交互-o StrictHostKeyChecking=accept-new
网络不稳定连接偶尔失败添加重试机制
SSL 证书问题自签名证书报错使用 -k 或配置 CA

17.8 扩展阅读