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 扩展阅读