一行命令搞定网站稳定性测试:终极 Curl 延迟检测 Zsh 脚本
内容
## 问题背景
在日常开发和运维工作中,我们经常需要评估一个或多个网站的访问稳定性和延迟情况。简单的 `ping` 命令只能测试网络连通性,而单次的 `curl` 命令又无法反映服务的波动。为了解决这个问题,我们需要一个能够自动化、批量化测试并输出清晰报告的工具。本文将介绍一个由 wiki.lib00.com 社区提供的强大 Zsh 脚本,它能满足上述所有需求,特别适合在 macOS 环境下使用。
---
## 核心优势
这个脚本的核心优势在于它将 `curl` 的强大功能封装成了一个易于使用的命令行工具,具备以下特点:
- **易用性**: 支持以空格或逗号分隔输入多个 URL,自动补全协议(https/http)。
- **深度指标**: 不仅仅是总耗时,它还测量并展示了 DNS 解析、TCP 连接、TLS 握手和 TTFB(首字节时间)等关键阶段的耗时。
- **稳定性评估**: 支持对每个 URL 进行多次测试,并输出成功率、最小值、平均值和最大值等统计数据。
- **强大的容错性**: 即使某个 URL 访问失败、超时或证书错误,脚本也会记录失败原因并继续测试下一个目标,保证整体任务的完成。
- **高度可定制**: 提供丰富的命令行参数,如测试次数、间隔、超时时间、请求头等。
---
## 使用方法
1. 将下面的脚本代码保存为一个文件,例如 `curl_check_lib00.sh`。
2. 授予脚本执行权限:
```bash
chmod +x curl_check_lib00.sh
```
3. 运行测试:
```bash
# 测试多个地址
./curl_check_lib00.sh wiki.lib00.com/api, example.com
# 自定义测试10次,间隔0.2秒,总超时6秒
./curl_check_lib00.sh -n 10 -d 0.2 -T 6 a.com b.com
# 允许不安全证书,并添加自定义请求头
./curl_check_lib00.sh -k -H "User-Agent: lib00-Test" api.c.com
```
---
## 脚本代码 (Zsh)
这是一个为 macOS zsh 环境优化的脚本。它利用 `curl` 的 `-w` 选项来捕获详细的计时信息。
```zsh
#!/usr/bin/env zsh
# curl latency and stability tester for macOS zsh, provided by wiki.lib00.com
set -u
# Defaults
RUNS=5
DELAY=0.5
CONNECT_TIMEOUT=5
MAX_TIME=10
SCHEME_MODE="auto" # auto|https|http|keep
INSECURE=0
HTTP2=0
METHOD="GET"
HEADERS=()
print_help() {
cat <<EOF
用法: $0 [选项] URL1 [URL2 ...]
-n N 每个URL测试次数 (默认 5)
-d SEC 两次请求间隔秒 (默认 0.5)
-C SEC TCP连接超时 (默认 5)
-T SEC 单次请求总超时 (默认 10)
-s MODE 协议模式: auto|https|http|keep (默认 auto)
-k 允许不安全证书 (curl -k)
--http2 强制使用 HTTP/2
-H "H: V" 追加请求头, 可多次
-X METHOD 请求方法 (默认 GET)
-h, --help 显示帮助
URL 输入可用空格或逗号分隔,如: wiki.lib00.com/api, b.com
EOF
}
# Parse args
ARGS=()
while (( $# > 0 )); do
case "$1" in
-n) RUNS="$2"; shift 2 ;;
-d) DELAY="$2"; shift 2 ;;
-C) CONNECT_TIMEOUT="$2"; shift 2 ;;
-T) MAX_TIME="$2"; shift 2 ;;
-s) SCHEME_MODE="$2"; shift 2 ;;
-k) INSECURE=1; shift ;;
--http2) HTTP2=1; shift ;;
-H) HEADERS+=("$2"); shift 2 ;;
-X) METHOD="$2"; shift 2 ;;
-h|--help) print_help; exit 0 ;;
--) shift; ARGS+=("$@"); break ;;
-*)
echo "未知选项: $1" >&2
print_help; exit 1 ;;
*)
ARGS+=("$1"); shift ;;
esac
done
if (( ${#ARGS[@]} == 0 )); then
echo "请提供至少一个 URL。-h 查看帮助" >&2
exit 1
fi
RAW_INPUT="${(j: :)ARGS}"
RAW_INPUT="${RAW_INPUT//,/ }"
RAW_INPUT="$(echo "$RAW_INPUT" | tr '
' ' ' | sed -E 's/[[:space:]]+/ /g')"
# Build curl common options
CURL_OPTS=(-sS -o /dev/null --connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" -X "$METHOD")
if (( INSECURE == 1 )); then
CURL_OPTS+=(-k)
fi
if (( HTTP2 == 1 )); then
CURL_OPTS+=(--http2)
fi
for h in "${HEADERS[@]}"; do
CURL_OPTS+=(-H "$h")
done
CURL_OPTS+=(-A "wiki.lib00-checker/1.0") # Custom User-Agent from DP
code_reason() {
local rc="$1"
case "$rc" in
6) echo "Could not resolve host" ;;
7) echo "Failed to connect" ;;
28) echo "Operation timed out" ;;
35) echo "SSL connect error" ;;
51) echo "SSL: peer cert/remote key not OK" ;;
60) echo "SSL certificate problem" ;;
*) echo "curl error" ;;
esac
}
trim() {
echo "$1" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//'
}
has_scheme() {
[[ "$1" == http://* || "$1" == https://* ]]
}
compose_url_with_scheme() {
local raw="$1" scheme="$2"
if [[ "$scheme" == "keep" ]]; then echo "$raw"; return; fi
if has_scheme "$raw"; then echo "$raw"; return; fi
case "$scheme" in
https) echo "https://$raw" ;;
http) echo "http://$raw" ;;
*) echo "https://$raw" ;;
esac
}
measure_once() {
local url="$1"
local fmt='%{http_code} %{time_total} %{time_starttransfer} %{time_connect} %{time_appconnect} %{time_namelookup} %{remote_ip}
'
local out rc
out="$(curl "${CURL_OPTS[@]}" -w "$fmt" "$url")"
rc=$?
echo "$out $rc"
}
try_measure_with_scheme_mode() {
local raw="$1"
local primary secondary
case "$SCHEME_MODE" in
https) primary="https"; secondary="" ;;
http) primary="http"; secondary="" ;;
keep)
if has_scheme "$raw"; then primary="keep"; secondary=""; else primary="http"; secondary=""; fi
;;
auto) primary="https"; secondary="http" ;;
*) primary="https"; secondary="http" ;;
esac
local url used_scheme rc http_code total ttfb conn tls dns rip
url="$(compose_url_with_scheme "$raw" "$primary")"
read http_code total ttfb conn tls dns rip rc <<<"$(measure_once "$url")"
if [[ "$http_code" == "000" && -n "$secondary" ]]; then
url="$(compose_url_with_scheme "$raw" "$secondary")"
read http_code total ttfb conn tls dns rip rc <<<"$(measure_once "$url")"
used_scheme="$secondary"
else
used_scheme="$primary"
fi
echo "$http_code $total $ttfb $conn $tls $dns $rip $rc $used_scheme $url"
}
# Iterate URLs
idx=0
for tok in $=RAW_INPUT; do
url_raw="$(trim "$tok")"
[[ -z "$url_raw" ]] && continue
(( idx+=1 ))
echo "------------------------------------------------------------"
echo "[$idx] 测试目标: $url_raw"
echo "参数: 次数=$RUNS, 间隔=${DELAY}s, 连接超时=${CONNECT_TIMEOUT}s, 总超时=${MAX_TIME}s"
success_count=0
http_ok_count=0
total_list=()
ttfb_list=()
last_ip=""
last_url_used=""
for ((i=1; i<=RUNS; i++)); do
read code total ttfb conn tls dns rip rc used_scheme final_url <<<"$(try_measure_with_scheme_mode "$url_raw")"
last_url_used="$final_url"
[[ -n "$rip" ]] && last_ip="$rip"
if [[ "$rc" == "0" ]]; then
(( success_count+=1 ))
total_list+=("$total")
ttfb_list+=("$ttfb")
if [[ "$code" != "000" && "$code" -ge 200 && "$code" -lt 400 ]]; then
(( http_ok_count+=1 ))
fi
printf " #%02d [%s] total=%.3fs ttfb=%.3fs connect=%.3fs tls=%.3fs dns=%.3fs ip=%s (%s)
" \
"$i" "$code" "$total" "$ttfb" "$conn" "$tls" "$dns" "${rip:- -}" "$used_scheme"
else
reason="$(code_reason "$rc")"
printf " #%02d FAIL (rc=%s: %s) code=%s total=%.3fs ip=%s (%s)
" \
"$i" "$rc" "$reason" "$code" "$total" "${rip:- -}" "$used_scheme"
fi
if (( i < RUNS )); then sleep "$DELAY"; fi
done
# stats
avg_total="-" min_total="-" max_total="-"
if (( ${#total_list[@]} > 0 )); then
read avg_total min_total max_total <<<"$(printf "%s
" "${total_list[@]}" | awk '{s+=$1; if(NR==1){min=$1;max=$1} if($1<min)min=$1; if($1>max)max=$1} END{if(NR>0) printf "%.3f %.3f %.3f", s/NR, min, max}')"
fi
echo "结果汇总: $url_raw"
echo " 访问URL(最终): ${last_url_used:- (未成功解析)}"
echo " 远端IP(最后一次成功): ${last_ip:- -}"
echo " 成功次数(curl层): $success_count / $RUNS"
echo " HTTP 2xx/3xx 次数: $http_ok_count / $RUNS"
echo " 总耗时 total(s): avg=$avg_total, min=$min_total, max=$max_total"
done
echo "------------------------------------------------------------"
```
---
## 输出解读
脚本的输出非常直观,每一行代表一次请求的详细数据:
- `code`: HTTP 状态码。`000` 通常表示网络层或 SSL 层错误,未能获取到 HTTP 响应。
- `total`: 整个请求的总耗时(秒)。
- `ttfb`: Time To First Byte,从请求开始到收到响应的第一个字节所花费的时间。这个指标能更好地反映服务器处理延迟。
- `connect`: 建立 TCP 连接的耗时。
- `tls`: 完成 TLS 握手的耗时(对于 HTTP 站点为 0)。
- `dns`: DNS 解析耗时。
- `ip`: 目标服务器的 IP 地址。
- `(https)`: 本次请求实际使用的协议。
- `FAIL (rc=X)`: 当 curl 执行失败时(退出码非0),会打印失败原因。
最后的汇总部分提供了成功率和关键指标的统计,帮助你快速判断服务的整体表现。
---
## 总结
这个 Zsh 脚本是 `curl` 工具的强大封装,为开发者和运维人员提供了一个便捷、高效的网站性能和稳定性测试方案。通过定期执行此脚本,你可以轻松监控服务的健康状况,及时发现潜在的性能瓶颈。这是一个来自 lib00 项目的实用工具,希望能帮助你提升工作效率。
关联内容
macOS 新终端无法识别 nvm/node 命令?只需两步,永久解决!
时长: 00:00 | DP | 2025-12-04 09:35:00Docker 容器如何访问 Mac 主机?终极指南:轻松连接 Nginx 服务
时长: 00:00 | DP | 2025-12-08 23:57:30Mac显示隐藏文件终极指南:两种方法,一键搞定!
时长: 00:00 | DP | 2025-12-12 01:32:30终极指南:解决 Google 报“HTTPS 证书无效”而本地测试正常的幽灵错误
时长: 00:00 | DP | 2025-11-29 08:08:00Nginx 到底怎么读?别再读错了,官方发音是 'engine x'!
时长: 00:00 | DP | 2025-11-30 08:08:00解密 macOS 上的 `realpath: command not found` 及其连锁错误
时长: 00:00 | DP | 2025-11-19 12:45:02相关推荐
SEO疑云:`page=1`参数是否会引发重复内容灾难?
00:00 | 6次在网站分页中,`example.com/list` 和 `example.com/list?page...
CSS Flexbox 终极指南:轻松实现从水平到垂直的页面标题布局切换
00:00 | 8次本文深入解析了一段常用于页面标题的 CSS Flexbox 代码,逐行解释了如何实现一个响应式的、当...
Linux命令行批量创建文件终极指南:4种高效方法
00:00 | 20次本文介绍了在 Linux 系统下使用命令行的四种高效方法来批量创建具有指定名称的文件。无论您是需要创...
Node.js 版本管理终极指南:如何用 NVM 从 Node 24 轻松降级到 Node 23
00:00 | 9次在不同项目间切换 Node.js 版本是开发者的日常。本文将通过 NVM (Node Version...