资讯详情

重定向脚本从原理到排错:从Shell到HTTP的完整指南

📅 2026/9/20 9:17:17 | 华诺云谱 👁 阅读
重定向脚本从原理到排错:从Shell到HTTP的完整指南
工作里一旦碰上接口迁移、域名更换、页面改版第一个要写的脚本往往就是重定向脚本。重定向这件事听起来简单就是把一个请求转到另一个地址可真要把它写成能批量处理、能自动检测、能稳定跑在定时任务里的脚本里面值得琢磨的地方不少。这篇我就按自己的实操经验把重定向脚本从原理到排错完整聊一遍对象是那些已经写过几行脚本、但还不太清楚重定向为什么有时候好用、有时候又莫名其妙踩坑的同学。1. 重定向到底在解决什么问题1.1 三种最常见的“转向”输入输出、URL跳转、程序流转先给一个整体认识。日常开发运维里说的重定向其实至少有三个层面。第一是 Shell 层面的输入输出重定向。Linux 哲学里“一切皆文件”命令的输入、输出、报错都是数据流默认分别接到键盘和屏幕上但你可以用大于号、小于号把这些流“转向”到文件或者其他命令上。这是最原始、用得最多的重定向形式也是“重定向脚本”这个称呼最常见的使用场景。第二是 HTTP 层面的地址跳转。浏览器访问一个 URL服务器返回 301、302 状态码和一个 Location 头浏览器再跟着 Location 去请求新的地址。这个层面最常见的需求就是域名换新、页面迁移、短链跳转脚本要做的往往不是“发起跳转”而是“检测跳转是否正常”“批量统计哪些链接发生了跳转”。第三是程序代码内部的流转控制。比如命令行工具互相调用时把前一个程序的标准输出作为后一个程序的标准输入再比如自动化测试脚本里把中间日志暂时写到临时文件跑完再统一合并。这类“转向”本质上还是文件描述符层的重定向只是包装了业务语义。理解了这三个层面再看热词里那些“hook重定向”“shell脚本for循环”“via脚本”就顺了。所谓 hook 重定向其实就是在程序执行链路上插入钩子把某些请求或事件转向到自定义逻辑里而 for 循环批量重定向则是把单条命令的重定向规则套到一组文件或一组 URL 上这也是脚本从“一行命令”进化为“一个工具”的关键一步。1.2 为什么要把重定向写进脚本而不是手动操作有人会问重定向不是一条命令就能搞定吗为什么非要写成脚本因为真实场景里重定向从来不是“转一次就结束”的事。我给你还原几个我实际遇到的场景。第一个是接口平台迁移。公司老网关一个月后下线几十个业务方都要切到新网关同时要求旧地址仍然能稳定跳转到新地址保留三个月观察期。一个接口一个接口手测不现实必须写脚本批量检测所有旧地址的响应码和 Location 头自动标记出跳转失败或者跳转目标不对的条目。第二个是日志采集任务。定时任务跑在凌晨后台进程要持续输出日志还要按天滚动保存同时不能在前台占用终端。这就需要把标准输出和标准错误分别重定向到日志文件还要处理追加而不是覆盖的问题。这个脚本写完之后要能在没人值守的情况下连续跑几个星期不炸。第三个是前端缓存清理后的验证。页面静态资源做了 CDN 域名切换要验证源站和 CDN 节点返回的是不是预期的 301以及跳转链路上有没有死循环。这种检测如果手动用浏览器开发者工具去看一次两次还行批量链接根本看不完。这些场景的共同点是单次重定向很简单但批量、自动、可重复、可观测才是脚本存在的意义。而写好这类脚本的关键就是对重定向的底层机制有清晰的认知。2. Shell重定向脚本把“转向”写进命令2.1 先搞懂文件描述符再说重定向Shell 重定向的本质是在进程运行时替换它的标准输入、标准输出、标准错误这三个默认数据通道。进程启动后会默认打开 0、1、2 三个文件描述符分别对应 stdin、stdout、stderr。先看一张最常用的符号速查表后面所有例子都建立在这张表上。符号作用常用写法示例覆盖写入文件echo hello /tmp/a.txt追加写入文件echo hello /tmp/a.log从文件读取输入cat /tmp/a.txt2标准错误单独重定向cmd 2 /tmp/err.log21标准错误合并到标准输出cmd /tmp/all.log 21输出和错误都重定向cmd /tmp/all.log12标准输出合并到标准错误cmd 12把字符串作为标准输入cat hi这里最容易混淆的就是21的书写顺序。正确的理解是Shell 解析重定向符号是从左到右执行的所以cmd /tmp/all.log 21表示先把标准输出指向文件再把标准错误指向标准输出当前指向的地方也就是同一个文件。如果你反过来写cmd 21 /tmp/all.log那就变成先把标准错误指向当前的标准输出此刻还是终端再把标准输出指向文件结果就是错误信息仍然打在屏幕上完全没有合并进去。我第一次写排障脚本时就在这上面栽过跟头日志文件里只有输出没有报错排查了半天才发现是顺序搞反了。这个细节很小但足以让一个看起来正确的脚本在关键时刻不输出关键信息。还有一个容易忽略的点在重定向时会立刻清空目标文件即使命令本身执行失败。这意味着如果你用重定向一个正在被程序持续写入的日志文件可能会把进程的写偏移量搞乱造成日志空洞甚至程序崩溃。实际项目里滚动日志一般用追加或者交给 logrotate 处理而不是简单用覆盖。2.2 日志场景后台运行加重定向的经典写法运维场景里最经典的重定向脚本就是让一个程序在后台稳定运行并把所有输出都落盘。标准写法长这样nohup /opt/app/start.sh /var/log/app/$(date %Y-%m-%d).log 21 拆开看每一段都是什么意思。nohup的作用是让进程忽略挂断信号即使关掉终端进程也不会被杀掉。用追加模式避免每天启动新实例时把前一天的日志清空。$(date %Y-%m-%d)会在执行时动态生成当天日期实现按天切分日志。21把错误输出合并到同一份日志方便后续统一排查。最后的把整个命令放到后台执行。这里还要补一个判断技巧启动后不要立刻离开先确认进程是不是真的起来了。最稳妥的检查方式是同时看进程状态和日志输出sleep 3 ps -ef | grep start.sh | grep -v grep tail -n 20 /var/log/app/$(date %Y-%m-%d).log第一次写这种脚本的新手最容易漏掉的是给日志目录建好写权限。很多系统都有私有临时目录清理机制比如/var/log/app里如果用的是带/tmp的路径系统重启后目录可能就没了。我习惯在脚本开头统一判断目录存在性LOG_DIR/var/log/app mkdir -p $LOG_DIR这几行代码看着不起眼但能省掉半夜被人叫起来处理“日志目录不存在”的麻烦。2.3 for循环批量重定向把单条命令变成批量工具热词里有“shell脚本for循环”这个组合几乎是重定向脚本的标配。重定向脚本不只有“把日志输出到文件”一种用法更多时候是把同一套重定向规则反复应用到一批文件上。比如我有几十个配置文件每个文件里都有一个旧 IP 地址需要批量替换成新 IP并把替换结果写到新目录。用脚本处理的话for 循环加输入输出重定向是很顺手的做法#!/bin/bash OLD_IP192.168.1.100 NEW_IP10.0.0.50 SRC_DIR./configs DST_DIR./configs_new mkdir -p $DST_DIR for file in $SRC_DIR/*.conf; do base_name$(basename $file) # 用 sed 替换把结果通过重定向写到新文件 sed s/$OLD_IP/$NEW_IP/g $file $DST_DIR/$base_name done这段脚本里sed命令的原始输出在终端上其实不会显示任何内容因为把标准输出直接转向到了目标文件。这个“输出转向”动作才是循环体里真正干活的部分。如果要求更严格一些可以在处理过程中把每个文件的处理状态也重定向到总日志方便回溯echo processed: $base_name $DST_DIR/process.log这里有一个经验点如果文件数量很大比如上万个请记得给脚本加进度输出否则你会觉得程序“卡死”了。最简单的方式就是用printf按固定行数输出一个进度。重定向脚本做批量任务时一个常见的反模式是“只处理不输出”跑完之后完全不知道它经历了什么出问题也没法回溯。我的习惯是至少留一份清单文件记录哪些成功、哪些失败、失败原因是什么。3. HTTP重定向脚本批量检测与自动化处理3.1 状态码的含义不必背但判断逻辑要写对HTTP 重定向和 Shell 重定向不同它不是数据流的转向而是请求的再路由。服务器返回一个重定向状态码同时带上新的地址客户端再发起第二次请求。这里几个状态码的使用场景我直接写成表格状态码语义典型场景客户端行为301永久移动域名更换、旧页面永久迁移多数客户端会缓存后续直接访问新地址302临时移动临时活动页、登录跳转不缓存建议每次都重新请求原地址307临时重定向临时换地址且维持原请求方法不允许改变请求方法308永久重定向永久换地址且维持原请求方法不允许改变请求方法写检测脚本时真正要关心的不是背下每个状态码而是判断逻辑是否合理。比如你要验证一个老接口是否还能正常工作并不是直接看它最终是否返回 200 就行而是要区分两种情况一种是原接口 200说明还没迁移一种是返回 301 并指向新接口也是符合预期的最怕的是返回 404 或者 500那才是真正出错的地方。我有一次写迁移验证脚本时只判断了最终状态码是否为 200结果有一批老地址 301 到新地址之后新地址本身也 404 了但因为最终请求被客户端自动跟随到了 404 页面检测结果里看到的是“最终 404”反而把中间状态搞混了。后来我改成同时记录“每次跳转的状态码”和“每一跳的 Location 地址”问题才真正暴露出来是因为跳转落地页的路径参数写错了。3.2 用curl脚本抓取重定向链curl 是检测 HTTP 重定向最顺手的工具。它有几个参数专门干这件事-L表示跟随重定向默认最多跟随 50 次。-I发起 HEAD 请求只拿响应头不下载正文。-o /dev/null丢弃响应体只保留状态码信息。-w自定义输出格式可以精确输出状态码、重定向地址、耗时等字段。-s静默模式不显示进度。--max-redirs限制最大重定向次数防止死循环。我写过一个简单的批量重定向检测脚本核心逻辑是这样#!/bin/bash CHECK_LISTurls.txt RESULT_LOGredirect_check.log while read -r url; do [ -z $url ] continue final_code$(curl -s -L -o /dev/null -w %{http_code} --max-redirs 10 $url) redirect_count$(curl -s -o /dev/null -w %{num_redirects} --max-redirs 10 $url) final_url$(curl -s -L -o /dev/null -w %{url_effective} $url) echo $url | code$final_code | redirects$redirect_count | final$final_url $RESULT_LOG done $CHECK_LIST这段脚本有几个细节是反复调出来的。第一-w %{num_redirects}这个输出项只有在启用了-L的情况下才有意义否则永远是 0。第二如果你只想知道“有没有发生重定向”和“最终去了哪里”可以精简成一条命令curl -s -L -o /dev/null -w 原始地址: %{url_effective}\n状态码: %{http_code}\n跳转次数: %{num_redirects}\n $url第三如果遇到 HTTPS 证书校验失败可以在测试环境加上-k跳过证书校验但生产环境检查时千万别加查出来的结果没有可信度。真实场景里这类脚本通常被挂在 Jenkins 或者 cron 上每天定时跑一遍把 URL 清单里所有链接的健康状态汇总出来。写完之后维护成本很低反而是在“URL 清单从哪来”这个问题上花的时间最多。我现在的做法是让各个业务方自己维护一份 URLs 的清单文件定时脚本读这个文件谁改动谁负责避免了脚本维护人一年到头追着别人要链接更新。3.3 用Python脚本实现更精细的重定向控制curl 适合快速执行和批量检查但如果要精细判断“哪一跳出了问题”Python 的 requests 库更顺手。requests 默认会自动跟随重定向直到拿到最终响应。要关掉自动跟随用allow_redirectsFalse这个参数import requests url https://example.com/old-page resp requests.get(url, allow_redirectsFalse) print(resp.status_code) print(resp.headers.get(Location))如果保持自动跟随但想知道中间经过哪些跳转可以从resp.history里取import requests url https://example.com/old-page resp requests.get(url) for item in resp.history: print(item.status_code, item.headers.get(Location)) print(final:, resp.status_code, resp.url)resp.history是一个响应对象列表按顺序记录了每次跳转的响应最后一项 resp 本身是最终页面。配合一个循环就能输出一条完整的跳转链。爬虫开发里这个判断非常重要。有些站点会用 JS 做动态跳转返回的 HTML 里塞了一段window.location.replace(...)代码这种 requests 是“看不见”的因为跳转发生在浏览器渲染阶段。遇到这种情况脚本要做的是去匹配 HTML 里的跳转关键词而不是依赖响应头里的 Location。import re js_redirect re.search(rwindow\.location\.(?:href|replace)\s*\s*[\]([^\])[\], resp.text) if js_redirect: real_url js_redirect.group(1)这个正则不是万能方案有些站点还会用window.location.href ...拼接变量的写法需要另外适配。但原理是一样的当响应头里没有标准 Location 字段时就去 HTML 源码里找跳转信号。写这类脚本时有一点必须注意requests 默认跟随重定向的次数也有上限超过后会抛出TooManyRedirects异常。如果检测的链接不可控最好在请求时显式设置一个合理的超时时间resp requests.get(url, timeout10, allow_redirectsTrue)这样遇到死循环重定向时脚本至少能在异常里退出而不是无限卡住。4. 常见问题与排查技巧实录4.1 脚本执行报错和 Windows 环境下的坑热词里有一串“无法将 xxx 项识别为 cmdlet、函数、脚本文件或可运行程序的名称”这个报错常见于 Windows 下 PowerShell 试图执行 Linux 风格脚本或者没有安装对应工具。这里不展开软件安装的问题只说重定向脚本自身常见的执行失败原因。如果你在 Linux 下执行./script.sh遇到Permission denied最常见的原因是脚本文件缺少可执行权限而不是脚本内容本身写错了。chmod x script.sh ./script.sh如果你是在 Windows 上写脚本再传到 Linux 上跑大概率会遇到bad interpreter: /bin/bash^M: No such file or directory。这个问题的根源是 Windows 记事本保存的换行符是\r\n而 Linux 认为是\n导致/bin/bash后面跟了一个看不见的回车字符。解决方式很简单sed -i s/\r$// script.sh这个命令会把所有行尾的\r删掉文件瞬间恢复正常。也可以换个思路直接在脚本里用bash script.sh这种显式解释器的方式调用绕开文件权限和解释器路径的干扰。另外Windows 的 bat 脚本里也有重定向。常见的写法是echo off echo [INFO] start task D:\logs\task.log 21注意 bat 脚本的用法与 Linux 类似但路径分隔符是反斜杠。如果一个 bat 脚本弹出来一个命令窗口闪退多半是脚本本身有语法错误或者路径不对。排查方法是临时在脚本最后加一行pause这样窗口会保留方便看报错信息。4.2 日志为空、找不到日志文件这是重定向脚本咨询服务里被问得最多的问题脚本跑了但没有产生日志或者日志文件存在但里面什么都没有。排查思路按优先级来。第一先确认重定向路径是否可写。脚本里如果用了重定向到/var/log/xxx.log但当前用户没有 /var/log 目录的写权限Shell 就会直接报错重定向失败。这个报错通常出现在终端上但如果你把这个报错本身重定向到了其他文件那也会导致日志为空。第二检查是否把误用成了导致文件每次都被覆盖。比如脚本循环里每次执行命令都用了那日志里最后只剩最后一条命令的输出看起来就像是日志几乎为空。第三注意输出缓冲。某些语言或命令比如 Python 的 print在重定向到文件时是有缓冲的不会立刻写入磁盘。你等十几秒看日志发现还是空的但程序明明在跑这通常是缓冲造成的。Python 里可以用-u参数关闭缓冲python3 -u /opt/scripts/task.py /tmp/task.log 21或者给 print 增加 flushprint(processing..., flushTrue)第四有些程序会 fork 出子进程然后父进程退出日志仍在写入但是 Shell 的重定向已经释放了。这种情况建议用nohup加的方式让子进程继承文件描述符日志不间断写入。4.3 HTTP重定向死循环与跳转倾斜HTTP 重定向检测脚本里最烦人的就是遇到死循环。比如 A 跳 BB 跳 CC 又跳回 A脚本如果不加限制会一直请求下去直到超时或崩溃。解决方法就是前面提到的--max-redirs参数或者 requests 的异常捕获。一个完整一点的检测脚本至少应该输出每一跳的信息并在中途卡住时打印出循环路径curl -s -L --max-redirs 10 -w 跳转次数: %{num_redirects}\n最终地址: %{url_effective}\n状态码: %{http_code}\n -o /dev/null $url 21如果num_redirects恰好等于你设置的最大值那基本可以判定循环太重了或者真的存在死循环。还有一个不算 bug 但容易被误判的情况临时重定向302搭配缓存导致的“跳转倾斜”。用户第一次访问旧地址拿到了 302 指向新地址但某些代理层、CDN 或浏览器可能会缓存这次 302 响应。过了一段时间即使你在源站把 302 换成了直接 200用户访问旧地址时仍然可能被缓存里的 302 带走。遇到这类“清理不干净”的问题不能只盯脚本要去排查中间层缓存策略比如给响应头增加Cache-Control: no-store。我自己的习惯是检测脚本里如果发现 302 的 Location 指向的地址最终又跳回旧域名域下立刻标黄告警不要等到上线前才处理。上线前处理这种问题一方面时间紧张另一方面排查深度往往不够容易漏掉缓存层。4.4 重定向脚本用好这两个习惯最后分享两个我用得最多的习惯不算技术但能少走很多弯路。第一个习惯是在所有脚本入口统一输出开始时间、结束时间、执行结果。重定向脚本往往被丢在后台或定时任务里没有这个习惯的话出问题后连“脚本到底有没有跑”都不知道。echo [$(date %Y-%m-%d %H:%M:%S)] start $0 ... 业务逻辑 ... echo [$(date %Y-%m-%d %H:%M:%S)] end $0, exit code: $?第二个习惯是重定向脚本一定要保留“提前退出”开关。测试阶段可以一次性处理所有数据但上线给业务方用的时候建议加一个前置参数或环境变量能只处理前 N 条记录。这样就算脚本被误触发也能快速收敛影响面不用等它跑完再收拾残局。我实际遇到过一次事故一个批量 URL 重定向检测脚本被人手动触发参数没给对直接跑完了整个链路中间还因为 CDN 缓存原因产生了一批错误跳转结果那份检测报告全废了。从那之后我给所有这类脚本都加了一个默认只跑 20 条的“试跑模式”确认逻辑无误再加参数跑全量。这个习惯帮我挡掉了不少麻烦也建议你试试。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。