简介:本资源是一份面向Linux中级使用者的文本处理工具深度学习文档,聚焦grep、sed、gawk三大核心命令的原理、语法与工程化应用,解决日志分析、配置批量修改、结构化数据提取等典型运维与开发痛点。文档以PDF格式单文件交付(共1个文件,342KB),内容组织清晰:grep部分系统梳理正则元字符(含基本集与扩展集)、POSIX字符类及高频选项;sed详解定址机制、流编辑命令(d/s/逗号范围/e/r等)及多点编辑技巧;gawk则覆盖字段处理、模式动作匹配、内置函数与控制结构,辅以大量可直接复用的命令实例。已有121人下载学习,读者可获得从基础语法到复杂场景的完整能力链——包括正则表达式实战要点、sed非交互式批量编辑范式、gawk结构化数据清洗与统计模板,特别适合需提升Shell自动化效率的运维工程师、DevOps人员及Linux进阶学习者。
1. 为什么你写的 grep 命令总在生产环境“查不到”?——这不是正则写错了,而是你没搞懂三工具的分工边界
你有没有遇到过这种场景:在日志里用grep -E "ERROR|FATAL"想抓所有严重错误,结果漏掉了一行带[ERR]的关键报错;或者用sed 's/old/new/g'批量替换配置文件,却把old_path和old_version全替成了new_path和new_version,导致服务启动失败;又或者写了个awk '{print $3,$1}'处理 nginx access.log,发现$3有时是状态码、有时是空格、有时是破折号,输出乱成一团。这不是你正则不熟,而是没吃透grep、sed、gawk这三个 Linux 文本处理“铁三角”的能力边界与协作逻辑。它们不是功能重叠的备选工具,而是按“匹配 → 编辑 → 结构化分析”严格分层的流水线:grep只负责筛选行(行级布尔判断),sed负责行内编辑(字符串级替换/删除/插入),gawk负责字段级结构化解析(列、分隔符、上下文关联)。本文不讲语法罗列,只带你用真实运维场景(日志清洗、配置提取、API响应解析)跑通一条可复现、可调试、可上线的文本处理链路——从命令选项怎么选、正则怎么写不翻车、到三工具如何嵌套调用避免管道地狱。适合每天和日志、配置、API返回打交道的运维、开发、测试工程师,尤其适合被grep -v | sed -e | awk -F套娃命令折磨过的新手和想把脚本写得更健壮的老手。
2. grep 不是“找字符串”,而是“行过滤器”:选项组合与正则避坑实战
grep的核心使命只有一个:对输入的每一行做“是否匹配”的布尔判断,并输出匹配行(或不匹配行)。它不修改内容、不拆字段、不计数——这些是sed和gawk的事。但正因为目标单一,它的选项设计极度精炼,选错一个就全盘失效。
2.1 必须掌握的 5 个核心选项:从-i到-z
| 选项 | 作用 | 典型场景 | 血泪经验 |
|---|---|---|---|
-i | 忽略大小写 | 查日志时grep -i "error"匹配ERROR/Error/error | 慎用于敏感字段:如grep -i "password"可能误匹配PasswordResetToken,应改用grep -w "password"或grep "\bpassword\b" |
-v | 反向匹配(输出不匹配行) | 过滤掉DEBUG日志:grep -v "DEBUG" app.log | 注意管道顺序:cat app.log | grep -v "DEBUG" | grep "ERROR"是先过滤再筛选,而grep "ERROR" app.log | grep -v "DEBUG"是先筛选再过滤,结果可能不同 |
-n | 显示行号 | 定位配置项位置:grep -n "^listen" nginx.conf | 配合-A/-B更实用:grep -n -A2 "server_name" nginx.conf输出匹配行及后两行,看清完整 server 块 |
-o | 仅输出匹配部分(非整行) | 提取 IP 地址:grep -oE "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log | 必须搭配-E或-P:基础 grep 不支持\d,[0-9]是唯一安全写法;-o会把一行中多个匹配拆成多行输出,需用tr '\n' ' '合并 |
-z | 将输入视为以 null 字节分隔(而非换行) | 处理含换行符的 JSON 字段:grep -z "error_code" response.json | tr '\0' '\n' | 这是绕过“单行限制”的关键:-z让 grep 把整个 JSON 当作一行处理,避免因换行被截断;但输出仍是 null 分隔,必须用tr转回换行 |
提示:
-z选项常被忽略,但它解决了grep最大的软肋——无法处理跨行匹配。例如 API 返回的 JSON 中"message": "user not found\nplease check id",普通grep "not found"会失败,而grep -z "not found"能命中。
2.2 正则表达式:别再用.*了,这 3 类模式才是生产环境刚需
grep默认使用基本正则(BRE),-E启用扩展正则(ERE),-P启用 Perl 正则(需 GNU grep)。生产环境强烈推荐-E,语法清晰且兼容性好。以下是高频且易错的三类模式:
① 精确单词匹配:用\b,不是空格
# ❌ 错误:grep "port" config.txt 可能匹配 "export_port"、"import_port" # ✅ 正确:grep -E "\bport\b" config.txt # \b 表示单词边界 # ✅ 更严谨:grep -w "port" config.txt # -w 选项等价于 \b...\b逻辑说明:\b匹配的是单词字符(字母、数字、下划线)与非单词字符之间的位置,比用空格分隔更鲁棒,尤其在行首/行尾/标点旁。
② 数字范围匹配:用{n,m},不是*
# ❌ 错误:grep "192.168.*" ip.log —— `*` 会贪婪匹配任意字符,可能匹配到 "192.168.1.1000" # ✅ 正确:grep -E "192\.168\.[0-9]{1,3}\.[0-9]{1,3}" ip.log # ✅ 更优:grep -E "192\.168\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"参数说明:[0-9]{1,3}表示 1~3 位数字,覆盖 0~999;但 IP 段实际是 0~255,所以第二版用(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)精确限定,避免匹配非法值如192.168.300.1。
③ 多关键词“或”逻辑:用|,但必须加括号
# ❌ 错误:grep -E "ERROR|FATAL" log.txt —— 在 BRE 下 `|` 是字面量,需转义;ERE 下虽可直接用,但优先级低 # ✅ 正确:grep -E "(ERROR|FATAL)" log.txt # 加括号明确分组 # ✅ 更安全:grep -E "ERROR|FATAL|CRITICAL" log.txt # 多个关键词并列,无需括号(| 是最高优先级)逻辑说明:|在 ERE 中是“或”操作符,但若与其它元字符混用(如ERROR|FATAL:.*),不加括号会导致ERROR或FATAL:.*被匹配,而非ERROR:或FATAL:。加括号(ERROR|FATAL)明确语义。
2.3 避坑:grep 的 4 个经典翻车现场
现象 1:grep "2024-05-20"在日志里查不到当天记录
→ 原因:日志时间戳含毫秒或时区,如2024-05-20T10:30:45.123+0800,-被当作字面量,但.和+是正则元字符,未转义导致匹配失败
→ 解决:用-F选项进行固定字符串匹配(禁用正则):grep -F "2024-05-20" app.log;或转义元字符:grep "2024\-05\-20" app.log
现象 2:grep -oE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"提取邮箱,结果包含多余符号
→ 原因:-o仅输出匹配部分,但正则中.+会贪婪匹配到行尾标点(如user@example.com.中的.)
→ 解决:用负向字符类限定结尾:grep -oE "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}[^a-zA-Z0-9._%+-@]" app.log \| sed 's/[^a-zA-Z0-9._%+-@]//g',或直接用gawk做字段校验
现象 3:grep -r "timeout" /etc/nginx/报错Permission denied,结果不全
→ 原因:-r递归时遇到无权限目录(如/etc/nginx/sites-enabled/的 symlink 指向/var/www/)会中断或跳过
→ 解决:重定向错误输出:grep -r "timeout" /etc/nginx/ 2>/dev/null;或用find+grep组合:find /etc/nginx -type f -readable -exec grep -l "timeout" {} \;
现象 4:ps aux | grep "java"总是显示 grep 自身进程
→ 原因:grep "java"进程名含java,被自身匹配
→ 解决:用字符类规避:ps aux | grep "[j]ava"([j]ava匹配java但不匹配[j]ava);或用pgrep:pgrep -f "java"
3. sed 不是“查找替换”,而是“流式编辑器”:地址范围、模式空间与保持空间深度解析
sed的本质是逐行读入、在模式空间(pattern space)中编辑、然后输出。它不关心上下文关联,只处理当前行——这是它和gawk的根本区别。理解sed的三大核心机制(地址定界、模式空间、保持空间),才能写出可维护的脚本。
3.1 地址范围:从单行到跨行块,精准控制编辑范围
sed的命令格式为[address]command,地址决定命令作用在哪几行。常见地址类型:
① 行号地址:最直观,但脆弱
# 替换第 5 行的 "old" 为 "new" sed '5s/old/new/' file.txt # 替换第 10 到 20 行 sed '10,20s/old/new/' file.txt血泪经验:硬编码行号在配置文件中极不可靠——添加注释、调整顺序都会让行号失效。生产环境应避免纯行号,改用模式地址。
② 模式地址:用正则定位,鲁棒性强
# 替换以 "listen" 开头的行中的端口 sed '/^listen/s/:80/:8080/' nginx.conf # 删除从 "server {" 到 "}" 的整个块(含两端) sed '/^server {/,/^}/d' nginx.conf逻辑说明:/^server {/,/^}/是地址范围,表示从匹配^server {的行开始,到匹配^}的行结束(含这两行)。d命令删除该范围内所有行。
③ 组合地址:实现复杂逻辑
# 仅在 "location /api" 块内替换 proxy_pass sed '/^location \/api/,/^}/s/proxy_pass.*;/proxy_pass http:\/\/backend:8080;/' # 删除空行和注释行(连续操作) sed '/^$/d; /^#/d' config.ini参数说明:;分隔多个命令,/^$/d删除空行,/^#/d删除注释行,两条命令在同一行执行,避免管道开销。
3.2 模式空间(Pattern Space)与保持空间(Hold Space):sed 的“内存模型”
sed有两块内存:模式空间(当前行)和保持空间(跨行暂存)。90% 的sed脚本只用模式空间,但处理跨行逻辑(如合并多行、条件删除)必须用保持空间。
典型场景:将 JSON 数组的每个对象转为单行
# 输入:JSON 数组,每行一个字段,对象间用空行分隔 # { # "name": "Alice", # "age": 30 # } # # { # "name": "Bob", # "age": 25 # } # sed 脚本:用保持空间暂存对象,遇空行输出 sed '/^$/!{H;d;}; x; s/\n//g; s/{/{ /g; s/}/ }/g' input.json逻辑说明:
/^$/!{H;d;}:如果不是空行,追加到保持空间(H),然后删除模式空间(d),不输出;x:交换模式空间与保持空间,此时模式空间是上一个对象的所有行;s/\n//g:删除换行符,合并为一行;s/{/{ /g; s/}/ }/g:美化括号格式。
提示:保持空间是
sed最难掌握的部分,但也是它区别于grep/awk的核心能力。新手可先用awk实现类似逻辑,熟手再攻坚sed保持空间。
3.3 避坑:sed 的 4 个致命陷阱
现象 1:sed 's/old/new/g' file.txt修改了原文件,但file.txt内容没变
→ 原因:sed默认输出到 stdout,不修改原文件;-i选项才就地编辑
→ 解决:sed -i 's/old/new/g' file.txt(GNU sed);BSD/macOS 用sed -i '' 's/old/new/g' file.txt
现象 2:sed '/^#/d' config.conf删除了所有以#开头的行,但#include指令也被删了
→ 原因:^#匹配所有行首#,包括合法的预处理指令
→ 解决:精确匹配注释行:sed '/^[[:space:]]*#/d' config.conf([[:space:]]*匹配零或多个空白符,确保#是第一个非空字符)
现象 3:sed 's/user=.*/user=root/' /etc/passwd把username字段也替换了
→ 原因:.*贪婪匹配到行尾,user=出现在username:x:0:0:root:/root:/bin/bash:/sbin/nologin中
→ 解决:用非贪婪思路,匹配到分隔符:sed 's/user=[^:]*:/user=root:/' /etc/passwd([^:]*匹配除:外的任意字符,停在第一个:前)
现象 4:sed -e '1,5d' -e 's/abc/def/' file.txt第二条命令对前 5 行无效,但预期是“先删再替换”
→ 原因:sed是流水线式处理,每行依次经过所有命令;1,5d删除前 5 行后,后续命令自然不作用于它们,但s/abc/def/仍会对剩余行执行
→ 解决:明确命令作用域,或用awk替代:awk 'NR>5{gsub(/abc/,"def")} 1' file.txt
4. gawk 不是“高级 sed”,而是“微型数据库引擎”:字段解析、关联数组与多文件处理
gawk的定位是结构化文本分析。当数据有明确分隔符(空格、制表符、冒号)、需要按列计算、跨行关联、或生成统计报表时,gawk是唯一选择。它的核心优势在于:字段自动切分、内置变量(NR/NF/FS)、关联数组(hash)、BEGIN/END 块。
4.1 字段解析:从默认空格分隔到自定义 FS,掌控数据结构
gawk默认以空白符(空格、制表符)分隔字段,但生产环境数据格式千变万化:
① 按指定分隔符切分:-F选项
# 解析 /etc/passwd(冒号分隔) gawk -F: '$3 >= 1000 && $3 < 65534 {print $1, $5}' /etc/passwd # 解析 CSV(逗号分隔,需处理引号) gawk -F',' '{gsub(/^"|"$/,"",$1); print $1}' data.csv参数说明:-F:设置字段分隔符为:;$3是第三字段(UID);$1是用户名,$5是 GECOS 描述。gsub(/^"|"$/,"",$1)删除$1首尾引号,处理 CSV 常见格式。
② 动态分隔符:用FS变量
# 处理混合分隔符(空格和等号) gawk 'BEGIN{FS="[ =]+"} {print $1, $2}' config.ini # 解析 nginx 日志(空格分隔,但请求字段含空格,需特殊处理) gawk '{ # 手动切分:前 6 个字段用空格,第 7 个字段从第 7 个空格后开始 n = split($0, a, " "); ip = a[1]; time = a[4] " " a[5]; request = ""; for(i=7; i<=n; i++) request = request a[i] " "; print ip, time, request }' access.log逻辑说明:split($0, a, " ")将整行按空格切分为数组a,再手动拼接request字段,避免FS=" "导致请求字段被错误切分。
4.2 关联数组:用键值对替代 if-else,实现高效去重与统计
gawk的关联数组(如count[$1]++)是其灵魂功能,让复杂逻辑变得简洁:
① 统计日志中各 IP 的访问次数
gawk '{count[$1]++} END{for (ip in count) print ip, count[ip] | "sort -k2 -nr"}' access.log逻辑说明:count[$1]++以第一字段(IP)为键,自增计数;END块遍历数组,| "sort -k2 -nr"将输出通过管道排序(按第二列数值降序)。
② 过滤重复行,保留第一次出现的
gawk '!seen[$0]++' file.txt血泪经验:!seen[$0]++是gawk经典单行去重。seen[$0]初始为 0(false),!0为 true,执行print;++后seen[$0]变 1;下次同内容行,!1为 false,跳过。此写法比sort -u更快,且保持原始顺序。
③ 多文件关联:用 FILENAME 区分来源
# user.txt: uid,name # group.txt: gid,groupname gawk 'FNR==NR{user[$1]=$2; next} {print $2, user[$1]}' user.txt group.txt逻辑说明:FNR==NR在处理第一个文件(user.txt)时为真,将uid->name存入user数组;next跳过后续命令;处理第二个文件(group.txt)时,$1是gid,$2是groupname,user[$1]查不到(因为 key 是 uid),此处应改为user[$2]或调整逻辑——这是常见错误,需根据实际字段对齐。
4.3 避坑:gawk 的 4 个隐蔽雷区
现象 1:gawk '{print $1}' file.txt输出为空,但cat file.txt显示有内容
→ 原因:文件是 Windows 格式(CRLF 换行),gawk将\r视为字段一部分,$1实际是value\r,打印时\r回车导致覆盖
→ 解决:用dos2unix file.txt转换;或gawk '{sub(/\r$/,""); print $1}' file.txt
现象 2:gawk '$3 > 100 {print}' data.txt对数字字段比较失败,所有行都被输出
→ 原因:$3是字符串,>做字典序比较(如"200"<"1000"),而非数值比较
→ 解决:强制转数值:gawk '$3+0 > 100 {print}' data.txt(+0触发类型转换);或用int($3) > 100
现象 3:gawk 'BEGIN{FS=":"} {print $1}' /etc/shadow报错Permission denied
→ 原因:/etc/shadow权限为--- --- ---,普通用户无法读取;gawk无法绕过系统权限
→ 解决:用sudo gawk 'BEGIN{FS=":"} {print $1}' /etc/shadow;或改用getent shadow获取可读信息
现象 4:gawk '{sum += $2} END{print sum}' data.txt输出0,但数据明显有值
→ 原因:$2字段为空或非数字,+=将sum初始化为""(空字符串),"" + 123结果为"123"(字符串),最终sum是字符串,END时print sum输出空
→ 解决:初始化sum=0:gawk 'BEGIN{sum=0} {sum += $2} END{print sum}' data.txt;或用NR>0{sum += $2}
5. 三工具协同:构建可调试、可复用的文本处理流水线
单个工具解决不了复杂问题。真正的生产力来自grep→sed→gawk的职责分明、管道衔接、错误隔离。下面以“从 Kubernetes Pod 日志中提取异常堆栈并统计 Top 5 异常类型”为例,展示工业级流水线。
5.1 流水线设计原则:分阶段、可验证、可中断
阶段 1:grep筛选候选行(降低数据量,避免后续工具处理无关内容)
阶段 2:sed清洗行内噪声(统一格式,为gawk结构化解析铺路)
阶段 3:gawk结构化分析与聚合(字段提取、关联、统计)
提示:每个阶段都应能独立运行并验证输出。例如
grep阶段后加| head -20查看样本,确认筛选逻辑正确,再接sed。
5.2 实战案例:K8s 日志异常分析流水线
需求:从kubectl logs my-pod输出中,提取 Java 异常的Exception类名(如NullPointerException),统计出现频次 Top 5。
步骤分解与命令:
# 1. grep 筛选:只保留含 "Exception" 或 "Error" 的行,且排除 INFO/WARN 级别日志 kubectl logs my-pod 2>/dev/null | \ grep -E "(Exception|Error)" | \ grep -v -E "(INFO|WARN|DEBUG)" | \ # 2. sed 清洗:删除时间戳、日志级别、线程名等前缀,只留异常消息 sed -E 's/^[^]]*\] //; s/^\[[^]]*\] //; s/^[^:]*:[[:space:]]*//' | \ # 3. gawk 解析:提取第一个 "Exception" 后的类名(如 "java.lang.NullPointerException" -> "NullPointerException") gawk '{ # 查找 Exception 后的第一个单词(类名) if (match($0, /Exception[^a-zA-Z0-9]*([a-zA-Z0-9_.$]+)(\.[a-zA-Z0-9_.$]+)*/)) { class = substr($0, RSTART + RLENGTH - length(substr($0, RSTART + RLENGTH)), length($0)) # 提取最后一个点后的类名(如 java.lang.NullPointerException -> NullPointerException) n = split(class, parts, /\./) if (n > 0) { exception = parts[n] count[exception]++ } } } END { # 按频次降序输出 Top 5 n = asorti(count, sorted, "@val_num_desc") for (i = 1; i <= (n < 5 ? n : 5); i++) { print sorted[i], count[sorted[i]] } }'关键参数与逻辑说明:
grep -E "(Exception|Error)":初步筛选含异常关键词的行;grep -v -E "(INFO|WARN|DEBUG)":排除低级别日志,减少噪声;sed -E 's/^[^]]*\] //; ...':用多个s///删除日志前缀,[^]]*匹配非]字符,精准定位]后内容;gawk中match()函数返回匹配位置,RSTART/RLENGTH获取子串坐标,substr()提取类名;split(class, parts, /\./)将类名按.切分,parts[n]取最后一段(如NullPointerException);asorti(count, sorted, "@val_num_desc")对关联数组按键值(频次)降序排序。
验证方法:
- 单独运行
kubectl logs my-pod | grep -E "(Exception|Error)" | head -10,确认原始日志含目标异常; - 加
| sed -E 's/^[^]]*\] //' | head -5,确认清洗后只剩异常消息; | gawk '{print $0}'查看gawk输入,确认格式干净。
5.3 错误隔离与调试技巧:让流水线不再“黑匣子”
技巧 1:用tee保存中间结果
kubectl logs my-pod | \ grep -E "(Exception|Error)" | tee /tmp/stage1.log | \ sed -E 's/^[^]]*\] //' | tee /tmp/stage2.log | \ gawk '...' > /tmp/result.txt这样可随时检查/tmp/stage1.log(筛选后)、/tmp/stage2.log(清洗后)内容,定位问题阶段。
技巧 2:用set -x调试 shell 脚本
#!/bin/bash set -x # 开启调试,每行命令执行前打印 kubectl logs my-pod 2>/dev/null | grep -E "(Exception|Error)" | ... set +x # 关闭调试技巧 3:用gawk的print调试内部逻辑
gawk '{ if (match($0, /Exception/)) { print "DEBUG: matched line:", $0 > "/dev/stderr" # 输出到 stderr,不影响 stdout # ... 其他逻辑 } }'6. 进阶:用 gawk 写一个可复用的日志分析模块,替代 80% 的临时脚本
gawk的BEGIN/END块和函数定义能力,让它能胜任小型脚本任务。下面提供一个可直接复用的log_analyzer.awk模块,封装了日志时间解析、异常提取、TopN 统计等通用功能,只需传入参数即可定制。
6.1 模块设计:参数驱动,专注核心逻辑
#!/usr/bin/gawk -f # log_analyzer.awk - 通用日志分析模块 # 调用方式:gawk -f log_analyzer.awk -v mode="exception" -v top=5 input.log # mode: "exception" | "ip" | "status" # top: 输出 Top N 条目 BEGIN { # 初始化 FS = " "; # 默认空格分隔 if (mode == "exception") { pattern = "Exception|Error"; field = "class"; } else if (mode == "ip") { pattern = "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+"; field = "ip"; } else if (mode == "status") { pattern = " [0-9]{3} "; field = "status"; } else { print "Usage: gawk -f log_analyzer.awk -v mode=\"exception|ip|status\" -v top=5 file.log" > "/dev/stderr"; exit 1; } top = (top == "" ? 10 : top); } # 主逻辑:按模式提取字段 { if (mode == "exception") { # 提取 Exception 后的类名 if (match($0, /Exception[^a-zA-Z0-9]*([a-zA-Z0-9_.$]+)/)) { class = substr($0, RSTART + RLENGTH, length($0)); n = split(class, parts, /\./); if (n > 0) { key = parts[n]; count[key]++; } } } else if (mode == "ip") { # 提取 IP(第一字段) if ($1 ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) { count[$1]++; } } else if (mode == "status") { # 提取 HTTP 状态码(第七字段,nginx 日志) if (NF >= 7 && $9 ~ /^[0-9]{3}$/) { count[$9]++; } } } END { # 通用 TopN 输出 n = asorti(count, sorted, "@val_num_desc"); for (i = 1; i <= (n < top ? n : top); i++) { printf "%s\t%d\n", sorted[i], count[sorted[i]]; } }6.2 使用示例与参数说明
① 提取 Top 5 异常类名
gawk -f log_analyzer.awk -v mode="exception" -v top=5 app.log # 输出:NullPointerException 127 # IllegalArgumentException 89 # ...② 统计 Top 3 访问 IP
gawk -f log_analyzer.awk -v mode="ip" -v top=3 access.log # 输出:192.168.1.100 2451 # 10.0.0.50 1892 # ...③ 分析 HTTP 状态码分布
gawk -f log_analyzer.awk -v mode="status" -v top=10 access.log # 输出:200 15678 # 404 2341 # ...6.3 模块增强:添加时间窗口与告警阈值
# 在 BEGIN 块中添加时间解析支持 BEGIN { # ... 原有初始化 if (time_window != "") { # 解析 time_window 格式如 "2024-05-20 10:00:00" split(time_window, t, /[ -:]*/) target_sec = mktime(t[1] " " t[2] " " t[3] " " t[4] " " t[5] " " t[6]) } } # 在主逻辑中添加时间过滤 { # 假设时间戳在第 4 字段,格式 [20/May/2024:10:3 <p> <a href="https://download.csdn.net/download/xxedts/90960466" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>