要用jq从URL中精准提取域名,最可靠的方式不是让jq直接解析URL,而是先用正则捕获或前置转换把URL整理成JSON字段,再用jq的过滤表达式取host部分。
很多人以为jq既然是JSON处理器,那处理URL肯定也顺手,其实不然,jq只认JSON结构,你丢给它一个裸的URL字符串,它只会报错或者原样返回,真正高效的做法,是把“提取域名”拆成两个动作:先用正则或字符串操作把host从URL里抠出来,再用jq做批量过滤和格式化,下面这套方法,能覆盖从单条URL到百万行日志的大多数场景。
jq解析url提取域名,为什么直接操作往往失败
先看一个典型的翻车现场:
echo "https://example.com/path" | jq '.'
jq会提示parse error: Invalid literal,原因很简单,jq的输入必须是合法的JSON,URL既不带引号,也不是对象,自然不满足要求,有些人会先把URL加引号再试:
echo '"https://example.com/path"' | jq -r '.'
这样jq能输出原字符串了,但你拿到的还是整个URL,没有拆出域名,业内专家指出,jq的职责边界在“结构处理”而不是“语法解析”,你让jq做它不擅长的事,结果就是绕弯路。
正确的思路是:把URL当作一个普通字符串,先用正则把域名部分捕获出来,再用jq对捕获结果做二次加工,这也是下面所有方法的基本前提。
把URL变成JSON:三种最实用的前置处理法
用python或node生成JSON,再交给jq
当你要处理的是几十上百条URL时,建议一次性把它们转成JSON数组,再交给jq,这样jq能发挥原生优势,批量操作。
python3 -c "import sys,json; urls=[line.strip() for line in sys.stdin if line.strip()]; print(json.dumps(urls))" < urls.txt | jq -r '.[]'
你还可以在python里直接解析域名,但那就绕过了jq,如果你就是想在管道里用jq,那就保持这种分工,node也可以做同样的事,但python写起来更短,大多数日志分析脚本里,这个前置步骤几乎成了惯例。
用jq正则capture直接匹配
如果只是几条URL,不想引入额外的解释器,可以用jq自带的capture函数,jq从1.5版本开始支持正则,配合-R参数按行读取原始文本,就能直接提取host。
echo 'https://blog.example.com.cn:8080/path?q=1' | jq -R -r 'capture("^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]://)?(?<host>[^/:?#]+)") | .host'
输出:
blog.example.com.cn
关键在于(?<host>...)命名捕获组,捕获后jq会生成一个对象,你可以直接取.host字段,这个正则已经考虑了协议可选、host不包含冒号和斜杠的情况,比简单的split("/")[2]健壮得多。
用sed/grep预处理后再交给jq
有时你面对的数据量极大,比如几个GB的nginx日志,这时候纯jq正则处理可能会变慢,因为jq的每次匹配都带着JSON解析开销,先用sed或grep做粗筛,效率会高一个量级。
sed -E 's#^[a-zA-Z][a-zA-Z0-9+.-]://##' urls.txt | cut -d/ -f1 | jq -R -r 'select(length > 0)'
sed负责剥掉协议,cut负责截取第一个斜杠前的部分,这时再交给jq,jq只需要做校验和格式化,压力小很多,这个组合在processing日志时很实用,尤其是当你还要统计域名出现次数的时候。
从JSON日志中精准提取域名的完整流程
实际场景里,我们处理得最多的并不是裸URL,而是JSON格式的访问日志,比如一行一条记录,里面有request_url字段:
{"ip":"1.2.3.4","time":"2026-01-01","request_url":"https://docs.example.com/api?from=alert"}
这种结构下,jq的威力才能彻底发挥出来。
第一步:过滤空URL和无效行
日志里总有空字段或脏数据,直接提取会报错,所以先用select把有效行筛出来:
jq -r 'select(.request_url != null and .request_url != "") | .request_url' access.json
第二步:用命名捕获组提取host
接着把提取逻辑接上去:
jq -r 'select(.request_url != null and .request_url != "") | .request_url | capture("^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]://)?(?<host>[^/:?#]+)") | .host' access.json
这条命令能直接输出纯字符串的域名列表,你也可以把scheme一起捕获,方便后续按协议分组。
第三步:按需拆分端口、路径和query
有些场景还需要端口、路径,把正则扩展一下:
jq -r '.request_url | capture("^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]://)?(?<host>[^/:?#]+)(:(?<port>[0-9]+))?(?<path>/[^?#])?") | [.scheme, .host, .port, .path] | @tsv'
输出结果用Tab分隔,方便导入表格,注意port和path是可选的,没匹配到就是null,可以后续用给默认值。
第四步:把提取结果接入统计管道
拿到域名列表后,下一步往往是统计次数,用jq自身就能完成排序和计数:
jq -r '.request_url | capture("...").host' access.json | sort | uniq -c | sort -rn
如果你不想离开jq,也可以用group_by:
jq -r '.request_url | capture("...").host' access.json | jq -s 'group_by(.) | map({host: .[0], count: length}) | sort_by(-.count)'
两种方式都行,前者更省内存,后者更符合jq风格,数据量不大时,用jq -s全部读入内存很简洁。
url提取域名命令,性能优化的几个关键点
很多人在处理几十万行日志时发现命令跑得很慢,其实多数情况下不是jq本身慢,而是用法不对,这里有几个可以明显提速的细节。
- 避免在循环里反复执行jq,每次执行jq都要启动一次进程,开销巨大,正确做法是把所有URL写进一个文件,用
jq -R按行处理,一次搞定。 - 尽量先用
grep过滤掉不相关行,比如你只关心https请求,先grep 'https://',再交给jq,能少解析很多数据。 - 不要滥用`.
匹配,正则里的.容易引起回溯变慢,尤其是URL特别长的时候,用[^/:?#]+`这种字符类替代,既精准又高效。 - 大文件优先用
sort | uniq -c统计,而不是group_by,因为group_by需要把整个数组加载进内存,几GB的文件直接内存溢出。
为了让你快速选择,这里列一张常用表达式对照表:
| 需求 | 推荐命令 | 说明 |
|---|---|---|
| 从JSON字段提取host | jq -r '.url \| capture("...").host' |
输入必须是合法JSON |
| 从纯文本列表提取host | jq -R -r 'capture("...").host' |
-R按行读取原始字符 |
| 提取host并过滤空值 | jq -r 'select(.url != "") \| .url \| capture("...").host' |
先过滤再提取 |
| 提取根域名(简化规则) | jq -R -r 'capture("(?<host>[^/:?#]+)").host \| split(".") \| .[-2:] \| join(".")' |
不适用于多级公共后缀 |
| 统计域名出现次数 | jq -r 'capture("...").host' file \| sort \| uniq -c \| sort -rn |
内存占用最低 |
注意表格中的\|是Markdown转义,实际命令里用。
jq从URL提取域名的边角问题
host里带用户名密码的情况
有些URL长这样:https://user:pass@example.com/path,之前的正则会把user:pass@example.com整体当成host,解决办法是在正则里增加一个可选的userinfo捕获组:
capture("^(?<scheme>[a-zA-Z][a-zA-Z0-9+.-]://)?(?:(?<userinfo>[^@/?#])@)?(?<host>[^/:?#]+)")
提取时仍然取.host,就能拿到干净的example.com。
URL里没有协议的情况
比如example.com/path或example.com,正则的scheme部分已经是可选,所以能正常匹配,但如果URL以开头,比如//example.com/path,则scheme部分匹配不到,host也能正确捕获。
如何提取真正的根域名
blog.example.com.cn这类多级域名,简单取最后两段会得到com.cn,要处理好,需要一份公共后缀列表,或者对已知后缀做特殊判断,如果只是内部统计,把规则写成“最后两段或三段”即可,不要期望jq里有一行内置命令能解决这个问题,它毕竟不是专业的URL解析库。
关于jq从URL中提取域名的常见问题
jq解析url提取域名,有没有更简单的内置函数?
没有,jq官方文档里没有提供url或domain之类的解析函数,最接近的做法是先用split("?")[0]去掉查询参数,再用split("/")[2]取域名,这种方式对简单URL有效,但遇到端口和协议缺失时会出错,相比之下,capture加正则是最通用的方案,建议直接掌握它。
url提取域名命令,怎么处理带端口和路径的复杂URL?
只要正则里把host部分定义为[^/:?#]+,它天然会停在端口、路径和查询参数之前,如果你还需要保留端口,可以在host后面加(:(?<port>[0-9]+))?捕获组,这样example.com:8080就能拆出host为example.com,port为8080。
一堆URL里混合了http和https,协议会影响提取结果吗?
不会,正则里scheme部分设计成可选的,而且只匹配合法协议字符,所以http://、https://、ftp://都会被跳过,最终只留下host部分,即使URL没有协议,也能正常提取,这也是这个正则比硬编码https更可靠的原因。
回到根本问题上:jq不是URL解析器,但你可以把它变成一个高效的域名提取工具,关键就是两条用命名捕获组把host抠出来,用-R或JSON流让jq处理原始行,掌握了这两个核心点,无论是单条URL还是海量日志,都不会再卡壳。