Bash进度读取循环遇空变量提前终止,无法显示完成提示
问题描述
通过RESTful API读取下载任务的进度状态并打印时遇到以下问题:
- 下载完成时,状态会从“running”变为“terminated regularly”,且API不再返回
progress字段 - 脚本中
$progress变量会因此为空,导致循环行为异常,无法执行“Download finished.”的输出
运行环境:MacOS Ventura 13.4.1、Bash 3.2.57(1),曾尝试用if语句、循环包裹及-z检查$progress,均未解决问题。期望实现正常显示进度,并在下载完成后给出完成提示。
原脚本:
while true; do status=$(curl -s -u admin:admin -H "Accept:application/json" -X GET \ -i http://1.2.3.4/sessionInformation/1/status | grep "Status" | sed 's/ "Status"://g' | sed 's/"//g') while [[ $status != "terminated regularly" ]]; do progress=$(curl -s -u admin:admin -H "Accept:application/json" -X GET \ -i http://1.2.3.4/sessionInformation/1/status | grep "Progress" | sed 's/["Progress":" %]//g') echo -ne "Download ${progress} % finished.\r" done echo "Download finished." break done
set -x输出片段:
+ progress=99 + echo -ne 'Download 99 % finished.\r' + [[ running, != \t\e\r\m\i\n\a\t\e\d\ \r\e\g\u\l\a\r\l\y ]] ++ curl -s -u admin:admin -H Accept:application/json -X GET -i http://1.2.3.4/sessionInformation/1/status ++ grep Progress ++ sed 's/["Progress":" %]//g' + progress=
问题分析
- 状态提取错误:原脚本通过
grep+sed提取状态时,未处理JSON字段后的逗号,导致$status值为running,(而非预期的running),使得[[ $status != "terminated regularly" ]]永远为真,内层循环无法退出 - 未实时更新状态:内层循环中未重新获取最新状态,即使API返回
terminated regularly,脚本也无法感知 - 重复API请求:每次循环分别调用两次
curl,既低效又可能触发API限流 - 空进度未处理:当
progress字段不存在时,直接输出空值,导致显示异常
解决方案
推荐使用jq工具解析JSON(比grep+sed更可靠,MacOS可通过brew install jq安装),同时优化循环逻辑,确保实时更新状态、减少API请求,并处理空进度的情况:
修正后的脚本:
#!/bin/bash # 配置参数 USER="admin" PASS="admin" API_URL="http://1.2.3.4/sessionInformation/1/status" # 每次请求间隔(避免频繁调用API) DELAY=1 while true; do # 单次请求获取完整JSON响应 response=$(curl -s -u "${USER}:${PASS}" -H "Accept:application/json" "${API_URL}") # 用jq提取状态(-r返回原始字符串,不含引号) status=$(echo "$response" | jq -r '.Status') # 提取进度,若字段不存在则返回空 progress=$(echo "$response" | jq -r '.Progress // empty') # 判断是否下载完成 if [[ "$status" == "terminated regularly" ]]; then # 清理当前行的进度输出,打印完成提示 echo -ne "\rDownload finished. \n" break fi # 根据进度是否存在,输出对应提示 if [[ -n "$progress" ]]; then echo -ne "\rDownload ${progress} % finished." else echo -ne "\rDownload in progress, waiting for status update..." fi # 延迟后再进行下一次查询 sleep "$DELAY" done
无jq兼容方案
若无法安装jq,可改用grep+sed解析JSON(需注意JSON格式的一致性):
#!/bin/bash USER="admin" PASS="admin" API_URL="http://1.2.3.4/sessionInformation/1/status" DELAY=1 while true; do response=$(curl -s -u "${USER}:${PASS}" -H "Accept:application/json" "${API_URL}") # 提取状态,去除引号和末尾逗号 status=$(echo "$response" | grep -o '"Status":"[^"]*"' | sed 's/"Status":"//;s/"//') # 提取进度,仅保留数字 progress=$(echo "$response" | grep -o '"Progress":[0-9]*' | sed 's/"Progress"://') if [[ "$status" == "terminated regularly" ]]; then echo -ne "\rDownload finished. \n" break fi if [[ -n "$progress" ]]; then echo -ne "\rDownload ${progress} % finished." else echo -ne "\rDownload in progress, waiting for status update..." fi sleep "$DELAY" done
关键改进点
- 单次
curl请求获取完整响应,减少API调用次数 - 实时更新状态,确保能及时感知下载完成
- 处理空进度的边界情况,避免显示异常
- 添加延迟,避免频繁请求给API服务器造成压力
内容的提问来源于stack exchange,提问作者dataprolet
相关产品推荐
相关产品推荐

