CLI数据分析【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址https://gitcode.com/gh_mirrors/mi/miller点击查看免费下载本篇技术指南讲解如何用 Millermlr处理kubectl get pods与helm list这类看起来像表格、实际空白结构各不相同的 Kubernetes 生态命令行输出。读完本文你将掌握 PPRINT 与 TSV 两种输入格式的选择依据、用clean-whitespace清洗制表符与空格混合输出的完整链路以及通过 NIDX 格式把解析出的字段如 release 名称安全地传给helm uninstall等下游命令的实战方案。问题背景为什么 kubectl / helm 输出需要专门处理kubectl和helm命令都会产生表格化的输出这类数据属于**以名称为索引name-indexed**的结构化数据——每一列都有一个列名NAME、STATUS、AGE等这正是 Miller 擅长的数据形态。Miller 可以对 CSV、TSV、PPRINT、NIDX 等格式进行解析、过滤、排序和字段提取。然而这两个命令的输出在空白字符whitespace结构上存在显著差异kubectl的输出是纯空格对齐的表格符合 Miller 的 PPRINT 格式helm list的输出则是制表符tab与空格space混合的产物既不是严格的 PPRINT也不是严格的 TSV。因此在真正用 Miller 处理它们之前必须先搞清楚输出中的空白到底是什么字符。下文将逐一拆解。kubectl 输出的空白结构纯空格对齐的 PPRINTkubectl get pods的输出长这样以命名空间my-namespace为例$ kubectl -n my-namespace get pods | head NAME READY STATUS RESTARTS AGE app-5mjwm4-274754k8468 0/1 Completed 0 6m51s app-5mjwm4-274754vdfnf 0/1 Completed 0 6m50s app-5mjwm4-0 1/1 Running 0 6h8m app-5mjwm4-27475nt9cc 0/1 Completed 0 6m53s app-5mjwm4-27474454-dc7wq 0/1 Error 0 16h app-5mjwm4-27475416-tv2ff 0/1 Completed 0 56s app-5mjwm4-2747541t7lgk 0/1 Completed 0 115s app-5mjwm4-27475245-7sg9r 0/1 Completed 0 171m app-5mjwm4-27475410-k4gcr 0/1 Completed 0 6m52s从视觉上看它是对齐的表格因此可以判断PPRINTPretty-printed tabular格式是解析它的合适选择。PPRINT 是 Miller 的一种输入/输出格式它要求列与列之间通过固定宽度的空格对齐表头行决定列名。如何验证空白结构原文档给出了三种办法把输出送入 vimkubectl -n my-namespace get pods | vim -然后在 vim 中执行:set list隐藏的空白字符会以可见符号显示通过cat -t查看——tab 字符会显示为^I通过bat -Abat 的显示所有字符模式查看。用上述任意方法检查后可以确认kubectl 输出中看似空白的部分实际上全部是空格字符没有制表符。这是它能被 PPRINT 格式直接解析的前提。为了进一步确认一个有用的做法是把表格化输出跑一遍格式转换器检查表头是否被正确识别为键key、其余行是否被正确识别为值value。例如用--ipprint读入、--ojson输出只取第一行$ kubectl -n my-namespace get pods | mlr --ipprint --ojson head -n 1 [ { NAME: app-5mjwm4-274754k8468, READY: 0/1, STATUS: Completed, RESTARTS: 0, AGE: 14m } ]这里有两个值得注意的细节--ipprint让 Miller 以 PPRINT 格式读入标准输入--ojson让输出为 JSONhead -n 1只保留第一条记录RESTARTS的值0在 JSON 输出中是不带引号的数字——因为 Miller 会对字段值做类型推断详见 mlrval_infer.go 相关的实现纯数字字符串被推断为整数类型而AGE这类含单位的字符串保持字符串类型。对 kubectl 输出做排序与过滤dhms2sec 将 AGE 变成可排序的秒数假设我们要把未完成非Completed状态的 Pod 按存在时长AGE排序。AGE 列的值形如6h22m、8h、56s是天时分秒days-hours-minutes-seconds缩写格式字符串直接排序并不准确例如8h会排在6h22m之前但16h与8h的字典序关系也不符合直觉。Miller 内置的 DSL 函数dhms2sec可以把这种格式转换成秒数从而获得可正确排序的数值。其实现位于 relative_time.go它会循环解析形如数字单位的片段其中单位d天× 86400、h小时× 3600、m分钟× 60、s秒× 1逐项累加得到总秒数也支持-前缀表示负值遇到无法识别的单位会返回错误如dhms2sec(6h22m): unrecognized unit x。完整的处理管道如下$ kubectl -n service-xyz get pods \ | mlr --pprint \ filter $STATUS ! Completed \ then put $AGESEC dhms2sec($AGE) \ then sort -n AGESEC NAME READY STATUS RESTARTS AGE AGESEC app1-1500-5mjwm4-0 1/1 Running 0 6h22m 22920 app1-1624-6dh711-0 1/1 Running 0 6h27m 23220 app1-1500-pqb9b4-0 1/1 Running 0 6h30m 23400 app1-gbwuwi-2747495lbtzg 0/1 Error 0 7h59m 28740 app1-gbwuwi-0 1/1 Running 0 8h 28800 app1-gbwuwi-27474955r8gq 0/1 Error 0 8h 28800 app1-gbwuwi-27474956rps8 0/1 Error 0 8h 28800 app1-gbwuwi-2747495q7fnz 0/1 Error 0 8h 28800 app1-gbwuwi-2747495vnxgn 0/1 Error 0 8h 28800 app1-gbwuwi-674ddcfd89-2jt64 2/2 Running 0 8h 28800 app3-5c79574b69-8njgr 2/2 Running 0 9h 32400 app3-5c79574b69-np2qj 2/2 Running 0 9h 32400 app3-a56i7c-0 1/1 Running 0 13h 46800 app3-a56i7c-587dfc99cf-zrr4t 2/2 Running 0 13h 46800 app2-1500-pqb9b4-274746pfbfd 0/1 Error 0 13h 46800 app2-1500-pqb9b4-274746jtz8t 0/1 Error 0 13h 46800 app2-1500-pqb9b4-274746pmmhq 0/1 Error 0 13h 46800 app2-1500-pqb9b4-27474624h8fp 0/1 Error 0 13h 46800 app2-1500-pqb9b4-2747462d8n96 0/1 Error 0 13h 46800 app2-1500-pqb9b4-2747462xnmcf 0/1 Error 0 13h 46800 app2-1500-pqb9b4-27474630-95668 0/1 Error 0 13h 46800 app1-1500-pqb9b4-sr5vd 2/2 Running 0 13h 46800 app1-1500-5mjwm4-27474454-dc7wq 0/1 Error 0 16h 57600 app1-1500-5mjwm4-667c6fc66d-b97m9 2/2 Running 0 16h 57600 app1-1624-6dh711-2747435h42j 0/1 Error 0 17h 61200 app1-1624-6dh711-27474370-ph25r 0/1 Error 0 17h 61200 app1-1624-6dh711-74fb5cf9d6-cl5tq 2/2 Running 0 17h 61200对管道各环节的说明filter $STATUS ! Completed使用 Miller DSL 的filter动词保留STATUS不为Completed的记录put $AGESEC dhms2sec($AGE)为每条记录新增字段AGESEC值为 AGE 折算后的秒数sort -n AGESEC按数值-n表示数值排序而非字典序对AGESEC升序排序于是最老的仍在运行的 Pod排在前面如6h22m → 22920秒注意输入命令中省略了--ipprint因为 Miller 在管道场景下会通过 record_reader_factory.go 之类的工厂逻辑进行格式推断这里以--pprint指定输出格式为 PPRINT同时按 PPRINT 读入。helm list 输出的空白结构制表符与空格混合的四不像helm list的输出更挑剔原文描述为 a bit fussier。先直接看原始输出$ helm list NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION appdev-an-sc-1500-5mjwm4 service-xyz 1 2022-03-28 11:33:05.389975262 0000 UTC deployed appdev-cloud-test-7.1.12 appdev-exyzv-load-a56i7c service-xyz 1 2022-03-28 14:45:35.44317196 0000 UTC deployed appdev-cloud-test-7.1.12 appdev-sa-sc-1500-pqb9b4 service-xyz 1 2022-03-28 14:24:33.978580048 0000 UTC deployed appdev-cloud-test-7.1.12 appdev-sa-sc-1624-6dh711 service-xyz 1 2022-03-28 10:09:05.966332699 0000 UTC deployed appdev-cloud-test-7.1.12 appdev-wertzxyffa-gbwuwi service-xyz 1 2022-03-28 19:47:34.96763583 0000 UTC deployed appdev-cloud-test-7.1.12 staging service-xyz 797 2022-03-28 18:39:34.005120936 0000 UTC deployed appdev-cloud-test-7.1.12注意两点各行之间并没有完全对齐例如第 2、5 行的deployed前比其它行多一个空格——这说明它不像是规整的 PPRINTUPDATED列内存在时区偏移量前导空格... 0000 UTC中的空格这一点会成为后续解析的麻烦。如果用 PPRINT 格式直接解析Miller 会报错$ helm list | mlr --ipprint --ojson cat mlr : mlr: CSV header/data length mismatch 7 ! 5 at filename (stdin) line 2.错误信息中的CSV header/data length mismatch 7 ! 5说明第一行表头被切出了 7 个字段某些列名内部被空格切分开了而数据行只被切出了 5 个字段字段数不一致导致 PPRINT 解析失败。接下来用cat -t看隐藏字符——tab 会显示为^I$ helm list | cat -t NAME ^INAMESPACE ^IREVISION^IUPDATED ^ISTATUS ^ICHART ^IAPP VERSION appdev-an-sc-1500-5mjwm4^Iservice-xyz^I1 ^I2022-03-28 11:33:05.389975262 0000 UTC^Ideployed^Iappdev-cloud-test-7.1.12^I appdev-exyzv-load-a56i7c^Iservice-xyz^I1 ^I2022-03-28 14:45:35.44317196 0000 UTC ^Ideployed^Iappdev-cloud-test-7.1.12^I appdev-sa-sc-1500-pqb9b4^Iservice-xyz^I1 ^I2022-03-28 14:24:33.978580048 0000 UTC^Ideployed^Iappdev-cloud-test-7.1.12^I appdev-sa-sc-1624-6dh711^Iservice-xyz^I1 ^I2022-03-28 10:09:05.966332699 0000 UTC^Ideployed^Iappdev-cloud-test-7.1.12^I appdev-wertzxyffa-gbwuwi^Iservice-xyz^I1 ^I2022-03-28 19:47:34.96763583 0000 UTC ^Ideployed^Iappdev-cloud-test-7.1.12^I staging ^Iservice-xyz^I797 ^I2022-03-28 18:39:34.005120936 0000 UTC^Ideployed^Iappdev-cloud-test-7.1.12^I真相大白Helm 的作者在输出中混用了 tab 和空格——列与列之间用 tab 分隔而 tab 之后又填充了若干空格用于视觉对齐。这种格式不是 PPRINTPPRINT 要求纯空格定宽对齐而这里用了 tab也不是严格的 TSVTSV 只允许 tab 作为分隔符不允许在字段值内部出现 tab但这里 tab 后又跟了空格而且部分字段值内部也含空格例如2022-03-28 11:33:05.389975262 0000 UTC本身含有空格。同样地用格式转换器来观察它的真实结构。先用--itsvTSV 读入试一下$ helm list | mlr --itsv --ojson head -n 1 [ { NAME : appdev-an-sc-1500-5mjwm4, NAMESPACE : service-xyz, REVISION: 1 , UPDATED : 2022-03-28 11:33:05.389975262 0000 UTC, STATUS : deployed, CHART : appdev-cloud-test-7.1.12, APP VERSION: } ]可以观察到三个问题键字段名带着尾部空格NAME 、NAMESPACE 、UPDATED 等——因为 tab 前的列名被右对齐填充了空格值带着前导/内部空格REVISION: 1 的值含有尾部空格APP VERSION: 这个值几乎全是空格tab 后紧跟空格所致UPDATED的键名极长含 32 个填充空格直接引用它会很痛苦。这正是 Miller 的clean-whitespace动词的用武之地它会对记录的每个字段把键和值的首尾空白剥离strip并把连续多处空白压缩为单个空格collapse。在 helm 场景下$ helm list | mlr --itsv --ojson clean-whitespace then head -n 1 [ { NAME: appdev-an-sc-1500-5mjwm4, NAMESPACE: service-xyz, REVISION: 1 , UPDATED: 2022-03-28 11:33:05.389975262 0000 UTC, STATUS : deployed, CHART: appdev-cloud-test-7.1.12, APP VERSION: } ]清洗之后键名变得干净NAME、NAMESPACE、CHART值也正确归位了。注意两个残余现象REVISION: 1 的值仍然带尾部空格——原因在于压缩空白不会删除单个空格1后的一串空格由 tab 后紧跟空格造成clean-whitespace只压缩多个空白为单个而这里的空格之间没有其他字符\s匹配的是连续的空白字符1与后续空格之间……实际上这里空格位于字符串尾部strip应该会去掉尾部空格。观察输出可知该行为与BIF_clean_whitespace的先 collapse 后 strip、再类型推断实现有关值1在 collapse 后变为1strip 只作用于首尾而该字段在键值清洗路径中经过了类型推断FromInferredType数值型字符串在推断后转成了 int 类型最终以字符串呈现时保留了字段原貌。这一细节属于 helm 输出与 clean-whitespace 交互的边界行为实际使用时建议结合strp/ssub等做二次处理STATUS 键名仍带一个尾部空格、APP VERSION的值为空串说明 helm 输出对部分行/列做了不一致的空白填充clean-whitespace已尽力将键值对齐到可正确识别的程度。从源码看clean-whitespace动词的实现位于 clean_whitespace.go它有三个工作模式默认模式不带选项同时清洗键和值逐字段调用BIF_clean_whitespace后重建记录见cleanWhitespaceInKeysAndValues-k | --keys-only只清洗键、不动值cleanWhitespaceInKeys-v | --values-only只清洗值、不动键cleanWhitespaceInValues。底层依赖的 DSL 级函数都在 strings.go 中lstripstrings.TrimLeft(..., \t)去掉左侧空格与 tabrstripstrings.TrimRight(..., \t)去掉右侧空格与 tabstripstrings.Trim(..., \t)去掉两侧空格与 tabcollapse_whitespace用正则\s把连续空白替换为单个空格clean_whitespacestrip(collapse_whitespace(x))的组合并通过FromInferredType做类型推断因此 42 会变成整数42。在clean-whitespace帮助输出中见 reference-verbs.md 与源码中的transformerCleanWhitespaceUsage官方明确提示-k与-v不能同时指定要同时清洗键和值就两者都不加。需要更细粒度控制时请使用 DSL 函数lstrip、rstrip、strip、collapse_whitespace、clean_whitespace。对 helm 输出做排序与过滤strptime systime 计算 release 年龄拿到干净数据后就可以按UPDATED列排序了。由于UPDATED形如2022-03-28 11:33:05.389975262 0000 UTC直接字符串排序在都是同一天的场景下恰好等价于时间排序但更稳妥、也更通用的做法是解析时间戳并计算与当前时刻的年龄差$ helm list \ | mlr --itsv --opprint clean-whitespace \ then put $AGESEC int(systime() - strptime($UPDATED, %Y-%m-%d %H:%M:%S.%f 0000 UTC)) \ then sort -n AGESEC \ then cut -x -f APP VERSION,UPDATED NAME NAMESPACE REVISION STATUS CHART AGESEC appdev-sa-sc-1624-6dh711 service-xyz 1 deployed appdev-cloud-test-7.1.12 30874 appdev-an-sc-1500-5mjwm4 service-xyz 797 deployed appdev-cloud-test-7.1.12 34955 appdev-sa-sc-1500-pqb9b4 service-xyz 1 deployed appdev-cloud-test-7.1.12 48993 appdev-xxyzv-load-a56i7c service-xyz 1 deployed appdev-cloud-test-7.1.12 50255 staging service-xyz 1 deployed appdev-cloud-test-7.1.12 60543 appdev-wertzxyffa-gbwuwi service-xyz 1 deployed appdev-cloud-test-7.1.12 65583这里涉及三个 Miller DSL 时间函数实现均在 datetime.gosystime()返回当前 Unix 时间戳浮点秒float64(time.Now().UnixNano()) / 1.0e9见 datetime.gostrptime(s, format)把字符串按给定格式解析为时间戳秒数浮点内部委托给pkg/pbnjay-strptime包实现见 datetime.go。格式串%Y-%m-%d %H:%M:%S.%f 0000 UTC中%f表示微秒/纳秒小数部分0000 UTC是字面量需要与 helm 输出中的时区表示逐字匹配int()把浮点秒差取整为整数秒便于排序与后续比较。此外还用到了cut -x -f APP VERSION,UPDATED-x表示排除exclude即从输出中去掉APP VERSION和UPDATED两列只保留其余字段让表格更聚焦sort -n AGESEC按数值升序排序得到年龄最小的 release 在最前、最老的 release 在最后。关于strptime的时区匹配有个注意点helm 的UPDATED值中部分行在0000前有空格... UTC与... UTC两种形态见上文cat -t输出中第 2、5 行UTC ^I与其它行UTC^I的差异。若直接strptime失败可先对UPDATED做字符串规整见下一节的ssub技巧或改用%Y-%m-%d %H:%M:%S.%f这类不含时区字面量的格式。提取字段交给下游命令NIDX 输出 cut 出 NAME 循环 helm uninstall最后一个典型场景把解析出的字段如 release 名称提取出来喂给其它命令——例如对太老的 helm release 执行helm uninstall。Miller 的NIDXIndex-numbered, toolkit style格式非常适合这种用途它输出一行一个值、不带字段名的纯文本列可以直接作为xargs/shell 循环的输入。切换方式是把输出格式从--opprint换成--onidx然后用cut -f NAME只保留NAME字段$ helm list \ | mlr --itsv --onidx clean-whitespace \ then put $UPDATED ssub($UPDATED, 0000 UTC, ) \ then put $AGESEC int(systime() - strptime($UPDATED, %Y-%m-%d %H:%M:%S.%f)) \ then sort -n AGESEC \ then cut -f NAME \ | tee names.txt appdev-sa-sc-1624-6dh711 appdev-an-sc-1500-5mjwm4 appdev-sa-sc-1500-pqb9b4 appdev-xxyzv-load-a56i7c staging appdev-wertzxyffa-gbwuwi这段管道与上一节相比有两点演进ssub($UPDATED, 0000 UTC, )先用ssub简单字符串替换非正则把UPDATED中的字面量0000 UTC含前导空格替换为空串消除时区偏移量及其前导空格——这正是文档开头提到的0000前面的空格是个问题的解法也让后续strptime的格式串简化成%Y-%m-%d %H:%M:%S.%f--onidx输出切到 NIDX 格式每行一个字段值cut -f NAME只输出NAME字段按字段名选取tee names.txt同时把结果写到文件并显示在终端。如果想要更严格的筛选可以在sort -n AGESEC之后追加then filter $AGESEC 86400即只保留年龄超过 86400 秒 24 小时的 release之类的过滤条件。最后拿到names.txt后就可以用 shell 循环逐个卸载$ for name in $(cat names.txt); do helm uninstall $name; done整个过程形成了一条完整、可审计的运维链路helm list→mlr清洗/解析/排序/筛选 → 提取 NAME → 批量卸载。你也可以把for循环换成xargs -n1 helm uninstall效果等价但for循环更便于在循环体内加入日志或条件判断。小结处理 kubectl / helm 输出的通用方法回顾本文可以提炼出一套可复用的方法论先诊断空白结构用cat -t看^I、vim - :set list或bat -A确认输出中到底是纯空格、纯 tab 还是混合再选对输入格式纯空格定宽对齐 →--ipprinttab 分隔值内无 tab→--itsv都无法直接解析时用--itsvclean-whitespace组合拳用格式转换器自检mlr --iXXX --ojson head -n 1可以快速确认键名和值是否正确归位让数据可排序、可比较dhms2sec处理AGE类时长strptimesystime处理时间戳并计算年龄ssub做必要的字符串规整面向下游命令输出--onidxNIDX 格式cut -f 字段名提取纯文本字段交给tee、xargs、shell 循环等外部工具。这些能力全部来自 Miller 对以名称为索引的数据的通用处理模型——kubectl、helm只是两个典型入口同样的思路稍加调整即可推广到其它 Kubernetes 生态命令如kubectl get nodes、helm status乃至任何类表格但空白结构不规整的 CLI 输出。相关格式细节可继续查阅 file-formats.mdPPRINT、TSV、NIDX 三节的格式规范动词与函数参考见 reference-verbs.md 与 reference-dsl-builtin-functions.md。赞分享CLI数据分析【免费下载链接】millerMiller is like awk, sed, cut, join, and sort for name-indexed data such as CSV, TSV, and tabular JSON项目地址https://gitcode.com/gh_mirrors/mi/miller点击查看免费下载相关推荐如何在5分钟内上手AI Powered Knowledge Graph Generator超简单安装与使用教程如何在5分钟内上手AI Powered Knowledge Graph Generator超简单安装与使用教程 AI Powered Knowledge Gr人工智能知识图谱数据可视化NLPMiller格式转换与数据清洗实战Miller格式转换与数据清洗实战 本文深入探讨了Miller工具在数据格式转换与清洗方面的强大功能。首先介绍了CSV/TSV/JSON三种常见格式之间的无缝转CLI数据分析MultiStatePage vs 传统方案为什么低侵入设计更值得选择MultiStatePage vs 传统方案为什么低侵入设计更值得选择 在Android开发中处理页面加载、空数据、错误等多状态切换是每个应用必备的功能。人工智能大模型AI 应用上一篇如何快速上手Finance Skills5分钟完成AI金融助手配置下一篇Llama模型资源限制终极指南配额管理与资源隔离策略创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考