当前位置: 首页 > news >正文

Bash 脚本逐行详解:一个HTTP请求与文件下载的示例

Bash 脚本逐行详解:一个HTTP请求与文件下载的示例

📋 目录

  • 脚本整体结构概览
  • 逐行逐函数详解
  • 核心知识点汇总
  • 二次开发指南
  • 常见问题与调试技巧

一、脚本整体结构概览

这个脚本是一个交互式数据获取器,用于演示如何通过命令行工具获取和下载网络资源。整体流程如下:

┌─────────────────────────────────────────────────────────┐ │ 1. 创建下载目录 │ │ 2. 进入无限循环(while true) │ │ ├─ 输入关键词(输入 q 退出) │ │ ├─ 输入子关键词 │ │ ├─ 搜索资源 → 获取资源ID │ │ ├─ 用ID获取真实下载链接 │ │ ├─ 提取文件扩展名 │ │ ├─ 下载文件 │ │ ├─ 检查HTTP状态码 │ │ └─ 移动到指定目录 │ │ 3. 退出循环,显示结束信息 │ └─────────────────────────────────────────────────────────┘

二、逐行逐函数详解

🔹 第 1 行:#!/bin/bash

#!/bin/bash

这行代码称为shebang,告诉操作系统脚本应该由哪个解释器来执行。此处指定使用bash

🔹 第 3 行:创建下载目录

mkdir-p/storage/emulated/0/下载的资源
  • mkdir: 创建目录的命令。
  • -p: 如果父目录不存在,则一并创建,且如果目录已存在,不报错。

🔹 第 12 行:变量拼接

name="${artist}-${song}"
代码段说明
name=定义变量name
"${artist}-${song}"字符串拼接。将artistsong-连接起来
${...}变量引用的标准写法,比直接$artist更安全

示例:

artist="示例歌手"song="示例歌曲"name="示例歌手-示例歌曲"

🔹 第 13-25 行:函数get_resource_search_results()

get_resource_search_results(){curl-s-XGET"https://api.example.com/resource/search?keyword=${artist}-${song}&page=1"\-H'User-Agent: Mozilla/5.0 (Linux; Android 14; Redmi K30 Pro Build/UKQ1.230804.001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36'\-H'Accept: */*'\--compressed}
📌 函数定义语法
函数名(){# 函数体}
📌curl命令详解

curl是一个强大的命令行网络请求工具,相当于命令行版的浏览器。

参数全称说明
-ssilent静默模式,不显示进度条和错误信息
-X GET指定请求方法为 GETGET 是获取数据的标准方法
-HHeader添加 HTTP 请求头,模拟浏览器行为
--compressed接受压缩响应告诉服务器可以发送 gzip 压缩的内容
📌 URL 解析
https://api.example.com/resource/search?keyword=示例歌手-示例歌曲&page=1
部分说明
https://协议,加密的超文本传输协议
api.example.com服务器域名(教学示例)
/resource/search接口路径
?keyword=示例歌手-示例歌曲查询参数:搜索关键词
&page=1查询参数:页码(page number)= 1
📌 HTTP 请求头解析

请求头是浏览器/客户端发给服务器的“自我介绍”。

头信息说明
User-Agent最重要的头!伪装成 Android 手机浏览器,让服务器以为是正常用户
Accept: */*接受任何类型的响应内容

🔹 第 27-30 行:函数extract_rid()

extract_rid(){echo"${1}"|awk-F'"''/\"id\"/{print $4; exit}'}
📌 函数参数

${1}是函数的第一个参数。

📌awk命令解析

假设搜索返回的 JSON 类似这样:{"status":1,"data":[{"id":"12345","name":"示例"}]}

  • -F'"': 将"作为字段分隔符。
  • '/\"id\"/{...}': 匹配包含"id"的行。
  • print $4: 打印第4个字段(即id的值)。

🔹 第 31-37 行:函数get_resource_url()

get_resource_url(){curl-s-XGET"https://api.example.com/resource/download?id=${1}"\-H'User-Agent: Mozilla/5.0 (Linux; Android 14; Redmi K30 Pro Build/UKQ1.230804.001) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36'\--compressed}

这个函数和get_resource_search_results()几乎一样,区别是:

比较项搜索函数下载URL获取函数
URLsearch?keyword=...download?id=...
参数关键词资源ID
返回搜索结果列表真实下载链接

流程关系:

搜索(关键词) → 获取资源ID → 用ID获取真实下载链接

🔹 第 39-44 行:函数get_file_extension()

get_file_extension(){case"${1}"in*.*)echo"${1##*.}";;*)echo'';;esac}
📌case语句
case"变量"in模式1)命令;;模式2)命令;;*)默认命令;;esac
📌${1##*.}—— 参数扩展

这是 bash 的高级字符串操作:

语法说明
${var##*.}从变量开头删除最长匹配的*.部分,保留剩余内容

示例:

url="https://example.com/resource/file.pdf"${url##*.}# 结果是:pdf

函数逻辑:

  • 如果 URL 包含.→ 提取扩展名(pdf/txt/json 等)
  • 如果 URL 不包含.→ 返回空字符串

🔹 第 46-50 行:函数download_resource()

download_resource(){echo"开始下载${name}.${2}..."curl-# ${1} >"${name}.${2}"echo"下载完成${name}.${2}."}
代码段说明
${name}使用外层定义的name变量(关键词-子关键词)
${1}第一个参数:下载链接
${2}第二个参数:文件扩展名
curl -#-#显示简单的进度条(# 号)
>重定向符号,把 curl 的输出保存到文件

🔹 第 52-55 行:主执行流程(调用函数)

search_result=$(get_resource_search_results)RID=$(extract_rid"${search_result}")resource_url=$(get_resource_url"${RID}")file_extension=$(get_file_extension"${resource_url}")
📌$(命令)—— 命令替换

$(命令)会执行括号内的命令,把输出结果作为字符串返回。

逐行解析:

代码说明
search_result=$(get_resource_search_results)调用搜索函数,把返回的 JSON 保存到search_result
RID=$(extract_rid "${search_result}")把搜索结果传给extract_rid,提取资源ID
resource_url=$(get_resource_url "${RID}")用资源ID获取真实下载链接
file_extension=$(get_file_extension "${resource_url}")从链接中提取文件扩展名

数据流:

关键词 → 搜索结果(JSON) → 资源ID → 下载URL → 扩展名

🔹 第 58-64 行:下载并检查状态

# 开始下载download_resource"${resource_url}""${file_extension}"# 检查下载进度,直到进度条达到100%progress=$(curl-s-o/dev/null-w"%{http_code}""${resource_url}")while["$progress"!="200"];dosleep1progress=$(curl-s-o/dev/null-w"%{http_code}""${resource_url}")done
📌curl -s -o /dev/null -w "%{http_code}"
参数说明
-s静默模式
-o /dev/null把响应体丢弃(/dev/null是“黑洞”)
-w "%{http_code}"write out,只输出 HTTP 状态码
📌 这个循环的问题说明(教学重点)

这段代码存在以下逻辑问题,是为了教学演示而保留的:

  1. 顺序错误:先执行了download_resource下载文件,然后才检查状态码。如果下载时出错,文件可能已经损坏。
  2. 无限循环风险:如果服务器一直返回非200状态码,这个循环会永远执行下去。

正确的做法应当是:先检查状态码,确认可以下载后再开始下载;或者给循环加上最大重试次数。

🔹 第 66 行:移动文件

mv-f./{*.pdf,*.txt,*.json}/storage/emulated/0/下载的资源
代码段说明
mvmove,移动文件
-fforce,强制覆盖,不提示
./当前目录
{*.pdf,*.txt,*.json}花括号扩展,匹配当前目录下所有 pdf/txt/json 文件
目标路径/storage/emulated/0/下载的资源

四、二次开发指南

🛠️ 改进方向 1:添加重试次数限制

# 改进后的下载检查(最多重试 5 次)max_retries=5retry_count=0progress=$(curl-s-o/dev/null-w"%{http_code}""${resource_url}")while["$progress"!="200"]&&[$retry_count-lt$max_retries];dosleep2progress=$(curl-s-o/dev/null-w"%{http_code}""${resource_url}")retry_count=$((retry_count+1))echo"第${retry_count}次重试..."doneif["$progress"=="200"];thendownload_resource"${resource_url}""${file_extension}"elseecho"下载失败,已重试${max_retries}次"continuefi

🛠️ 改进方向 2:支持批量下载(从文件读取列表)

创建列表文件resources.txt

示例歌手-示例歌曲 另一作者-另一作品 第三作者-第三作品

改进脚本:

#!/bin/bashmkdir-p/storage/emulated/0/下载的资源# 从文件读取,每行格式:关键词-子关键词whileIFS=read-rline||[-n"$line"];do# 跳过空行和注释行[-z"$line"]&&continue[["$line"=~^# ]] && continue# 用 - 分割主关键词和子关键词artist="${line%%-*}"song="${line#*-}"name="${line}"echo"正在处理:${name}"# ... 原来的搜索、下载逻辑 ...done<"resources.txt"

🛠️ 改进方向 3:添加下载进度显示

download_resource(){echo"开始下载${name}.${2}..."curl--progress-bar-o"${name}.${2}""${1}"echo""echo"下载完成${name}.${2}."}

🛠️ 改进方向 4:添加日志记录

LOG_FILE="download.log"log(){echo"[$(date'+%Y-%m-%d %H:%M:%S')]$1"|tee-a"$LOG_FILE"}# 使用方式log"开始下载:${name}"log"下载失败: HTTP${progress}"
代码段说明
date '+%Y-%m-%d %H:%M:%S'获取当前时间
tee -a同时输出到屏幕和追加到文件

🛠️ 改进方向 5:支持自定义下载目录

read-p"请输入下载目录(默认 /storage/emulated/0/下载的资源):"download_dirdownload_dir="${download_dir:-/storage/emulated/0/下载的资源}"mkdir-p"$download_dir"# 修改移动命令mv-f./{*.pdf,*.txt,*.json}"$download_dir"

🛠️ 改进方向 6:使用更健壮的 JSON 解析

原代码的awk方法不够健壮,推荐安装jq工具:

# 安装 jq(需要管理员权限)# apt install jq# 改进后的 extract_ridextract_rid(){echo"${1}"|jq-r'.data[0].id // empty'}

🛠️ 改进方向 7:添加文件存在性检查

download_resource(){localfilename="${name}.${2}"if[-f"$filename"];thenread-p"文件${filename}已存在,是否覆盖?(y/n): "confirmif["$confirm"!="y"];thenecho"跳过下载"return1fifiecho"开始下载${filename}..."curl-# "${1}" > "$filename"echo"下载完成${filename}."}
代码段说明
[ -f 文件 ]检查文件是否存在
local定义局部变量(只在函数内有效)
return 1返回错误码(非 0 表示出错)

📝 完整改进版脚本示例

#!/bin/bash# ========== 配置区 ==========DOWNLOAD_DIR="/storage/emulated/0/下载的资源"MAX_RETRIES=3LOG_FILE="download.log"# ===========================# 创建下载目录mkdir-p"$DOWNLOAD_DIR"# 日志函数log(){echo"[$(date'+%Y-%m-%d %H:%M:%S')]$1"|tee-a"$LOG_FILE"}log"========== 资源下载器启动 =========="whiletrue;doread-p"请输入主关键词(退出输入 q ):"artist["$artist"=="q"]&&breakread-p"请输入子关键词(空格输入 _ ):"songname="${artist}-${song}"log"开始搜索:${name}"# 搜索资源search_result=$(curl-s-XGET"https://api.example.com/resource/search?keyword=${artist}-${song}&page=1"\-H'User-Agent: Mozilla/5.0 (Linux; Android 14; Redmi K30 Pro) AppleWebKit/537.36 Chrome/117.0.0.0 Mobile Safari/537.36'\-H'Accept: */*'\--compressed)# 提取资源IDRID=$(echo"${search_result}"|awk-F'"''/\"id\"/{print $4; exit}')if[-z"$RID"];thenlog"错误: 未找到资源${name}"continuefilog"获取到资源ID:${RID}"# 获取下载链接resource_url=$(curl-s-XGET"https://api.example.com/resource/download?id=${RID}"\-H'User-Agent: Mozilla/5.0 (Linux; Android 14; Redmi K30 Pro) AppleWebKit/537.36 Chrome/117.0.0.0 Mobile Safari/537.36'\--compressed)if[-z"$resource_url"];thenlog"错误: 无法获取下载链接"continuefilog"下载链接:${resource_url}"# 提取扩展名if[["$resource_url"==*.*]];thenfile_extension="${resource_url##*.}"elsefile_extension="txt"fifilename="${name}.${file_extension}"# 检查文件是否已存在if[-f"$filename"];thenread-p"文件${filename}已存在,是否覆盖?(y/n): "confirm["$confirm"!="y"]&&continuefi# 下载文件(带重试)log"开始下载:${filename}"retry_count=0download_success=falsewhile[$retry_count-lt$MAX_RETRIES];doifcurl-fSL-o"$filename""$resource_url";thendownload_success=truebreakfiretry_count=$((retry_count+1))log"下载失败,第${retry_count}次重试..."sleep2doneif["$download_success"=true];thenmv-f"$filename""$DOWNLOAD_DIR"log"下载成功:${filename}"elselog"下载失败:${filename}(已重试${MAX_RETRIES}次)"rm-f"$filename"fidonelog"========== 资源下载器结束 =========="echo"感谢使用!"

http://www.jsqmd.com/news/1147172/

相关文章:

  • 2026年电商打包静音胶带源头厂家哪家强?附行业前5排名!
  • Fedora AI桌面计划搁浅:社区反对的不是AI,而是“强制统一“
  • FFI 外部函数接口详解
  • 盈启鲲鹏数字人直播区域代理
  • PyTorch Conv1d/Conv2d 实战:3个维度差异与5个应用场景代码对比
  • Wand-Enhancer:三步解锁Wand专业版完整功能,告别付费订阅
  • 三步解锁Wand专业版:免费享受完整游戏修改功能
  • AutoCAD 2024系统级安装与稳定运行全指南
  • 低代码平台构建QMS质量管理系统:从来料检验到全流程追溯的技术实践
  • 机器人世界模型的结构化正则性与泛化机制实操指南
  • 钢结构幕墙安装检测三维扫描
  • 压电警报系统硬件选型与多环境适应性设计
  • 基于TB6593FNG与PIC18F46K22的直流电机控制系统设计
  • ADP5350与PIC18F45K80的嵌入式电源管理方案
  • 美加墨世界杯赛事数据需求爆发,纳米数据为多场景提供实时支撑
  • 【ROS2】混合架构实战:用 --network host 打通 Docker 与宿主机的“任督二脉”
  • 丰益捷智慧食堂管理方案,筑牢后勤单位食堂食材全流程安全防线!
  • 本人已回归,请求恢复原力值
  • 如何免费解锁NVIDIA显卡隐藏性能:NVIDIA Profile Inspector终极指南
  • Go 入门到精通-13-指针与内存
  • 2026 BI 选型核心:“零代码 + AI” 如何帮企业各团队拿到实在价值?
  • 海外子公司法务财务HR外包,选哪家企业服务商?
  • WarcraftHelper:魔兽争霸III老游戏完美适配现代电脑的终极指南
  • 44、<简单>数字方阵-3
  • TensorFlow 2.x 神经网络训练实战:3步编译与fit()函数10个关键参数详解
  • 2023版二类医疗器械目录查询实战:NMPA与数屿医械数据库3步对比
  • 神器!完整网站下载器可下载任意网站源代码及资源,附在线演示和部署方法
  • A3910与PIC18F46K42电机驱动方案解析
  • 避坑与增效:2026年度企业级AI模型API中转平台调研复盘
  • 【助睿实战】大作业三:自媒体运营分析