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}字符串拼接。将artist和song用-连接起来${...}变量引用的标准写法比直接$artist更安全示例artist示例歌手song示例歌曲name示例歌手-示例歌曲 第 13-25 行函数get_resource_search_results()get_resource_search_results(){curl-s-XGEThttps://api.example.com/resource/search?keyword${artist}-${song}page1\-HUser-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\-HAccept: */*\--compressed} 函数定义语法函数名(){# 函数体}curl命令详解curl是一个强大的命令行网络请求工具相当于命令行版的浏览器。参数全称说明-ssilent静默模式不显示进度条和错误信息-X GET指定请求方法为 GETGET 是获取数据的标准方法-HHeader添加 HTTP 请求头模拟浏览器行为--compressed接受压缩响应告诉服务器可以发送 gzip 压缩的内容 URL 解析https://api.example.com/resource/search?keyword示例歌手-示例歌曲page1部分说明https://协议加密的超文本传输协议api.example.com服务器域名教学示例/resource/search接口路径?keyword示例歌手-示例歌曲查询参数搜索关键词page1查询参数页码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-XGEThttps://api.example.com/resource/download?id${1}\-HUser-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##*.}从变量开头删除最长匹配的*.部分保留剩余内容示例urlhttps://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_resultRID$(extract_rid ${search_result})把搜索结果传给extract_rid提取资源IDresource_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})donecurl -s -o /dev/null -w %{http_code}参数说明-s静默模式-o /dev/null把响应体丢弃/dev/null是“黑洞”-w %{http_code}write out只输出 HTTP 状态码 这个循环的问题说明教学重点这段代码存在以下逻辑问题是为了教学演示而保留的顺序错误先执行了download_resource下载文件然后才检查状态码。如果下载时出错文件可能已经损坏。无限循环风险如果服务器一直返回非200状态码这个循环会永远执行下去。正确的做法应当是先检查状态码确认可以下载后再开始下载或者给循环加上最大重试次数。 第 66 行移动文件mv-f./{*.pdf,*.txt,*.json}/storage/emulated/0/下载的资源代码段说明mvmove移动文件-fforce强制覆盖不提示./当前目录{*.pdf,*.txt,*.json}花括号扩展匹配当前目录下所有 pdf/txt/json 文件目标路径/storage/emulated/0/下载的资源四、二次开发指南️ 改进方向 1添加重试次数限制# 改进后的下载检查最多重试 5 次max_retries5retry_count0progress$(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_count1))echo第${retry_count}次重试...doneif[$progress200];thendownload_resource${resource_url}${file_extension}elseecho下载失败已重试${max_retries}次continuefi️ 改进方向 2支持批量下载从文件读取列表创建列表文件resources.txt示例歌手-示例歌曲 另一作者-另一作品 第三作者-第三作品改进脚本#!/bin/bashmkdir-p/storage/emulated/0/下载的资源# 从文件读取每行格式关键词-子关键词whileIFSread-rline||[-n$line];do# 跳过空行和注释行[-z$line]continue[[$line~^# ]] continue# 用 - 分割主关键词和子关键词artist${line%%-*}song${line#*-}name${line}echo正在处理:${name}# ... 原来的搜索、下载逻辑 ...doneresources.txt️ 改进方向 3添加下载进度显示download_resource(){echo开始下载${name}.${2}...curl--progress-bar-o${name}.${2}${1}echoecho下载完成${name}.${2}.}️ 改进方向 4添加日志记录LOG_FILEdownload.loglog(){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} $filenameecho下载完成${filename}.}代码段说明[ -f 文件 ]检查文件是否存在local定义局部变量只在函数内有效return 1返回错误码非 0 表示出错 完整改进版脚本示例#!/bin/bash# 配置区 DOWNLOAD_DIR/storage/emulated/0/下载的资源MAX_RETRIES3LOG_FILEdownload.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[$artistq]breakread-p请输入子关键词空格输入 _ songname${artist}-${song}log开始搜索:${name}# 搜索资源search_result$(curl-s-XGEThttps://api.example.com/resource/search?keyword${artist}-${song}page1\-HUser-Agent: Mozilla/5.0 (Linux; Android 14; Redmi K30 Pro) AppleWebKit/537.36 Chrome/117.0.0.0 Mobile Safari/537.36\-HAccept: */*\--compressed)# 提取资源IDRID$(echo${search_result}|awk-F/\id\/{print $4; exit})if[-z$RID];thenlog错误: 未找到资源${name}continuefilog获取到资源ID:${RID}# 获取下载链接resource_url$(curl-s-XGEThttps://api.example.com/resource/download?id${RID}\-HUser-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_extensiontxtfifilename${name}.${file_extension}# 检查文件是否已存在if[-f$filename];thenread-p文件${filename}已存在是否覆盖(y/n): confirm[$confirm!y]continuefi# 下载文件带重试log开始下载:${filename}retry_count0download_successfalsewhile[$retry_count-lt$MAX_RETRIES];doifcurl-fSL-o$filename$resource_url;thendownload_successtruebreakfiretry_count$((retry_count1))log下载失败第${retry_count}次重试...sleep2doneif[$download_successtrue];thenmv-f$filename$DOWNLOAD_DIRlog下载成功:${filename}elselog下载失败:${filename}已重试${MAX_RETRIES}次rm-f$filenamefidonelog 资源下载器结束 echo感谢使用​