在 Raku 中玩转 Shell:`‑ne` 处理日志行无可替代
原文:Shelling in Raku: -ne is unequaled for log lines
发布日期:2026‑08‑06
把 raku‑intellij‑plugin 适配到 Intellij IDEA 2026.1 的过程开局十分坎坷。
不知什么原因,用户升级 IDEA 之后总会破坏和 rip 的兼容性,即便在我看来,该做的兼容工作我全都完成了,力求做到最大程度向前兼容(“只要没崩就不用改……”)。
最后我只能盯着一套耗时超过一小时才能跑完的测试套件。(后来我们把耗时压到了 2.5 分钟甚至更短,但这是另一个故事。)持续跟踪测试日志体验很差:日志噪音巨大,测试文件名淹没在密密麻麻的输出字符之中。
于是我用上了 Raku,这套方案效果好到我想分享给大家。
关键就在 -n 参数
fish
set -gx LOG ".intellijPlatform/sandbox/raku-intellij-plugin/IU-2026.1/log-test/idea.log"
tail -f $LOG | raku -ne '.say if /"exit dumb mode"/ && not /light_temp/'上面命令含义:读取 tail -f $LOG 的每一行输出,执行 -e 后面的代码。
另一处省字符的巧妙设计来自 Raku 的主题变量,用起来近乎魔法。
当语法解析器看到裸方法调用,没有写调用者时,就会在当前词法作用域的主题变量上调用该方法,所以直接写
.say。后缀条件
if后面直接放正则表达式,正则会自动匹配当前主题变量。这里的not也完全符合直觉。
谁能想到呢?
这种裸正则表达式,叫做可延迟求值(thunkable)表达式。这块语法细节有些绕。但语言设计者预判用户会偏爱简洁写法,在这些位置,会把可延迟表达式包裹进代码块,通常使用主题变量(完整名字就是 $_)去执行。
增加业务处理逻辑
fish
tail -f $LOG | raku -ne '(++$, .split(" ")[0,1,*-1]).join(" ").say if /"exit dumb mode"/ && not /light_temp/'说实话,第一次见到 ++$ 这种写法,我脑子很难理解这居然是合法语法。但一旦掌握匿名状态变量之后,你会发现在别的语言里找不到它,会无比怀念。
在这个例子,我们不需要额外声明,只要对 $ 做自增 ++$,就得到一个计数器,用来格式化输出测试日志。状态变量非常好用,有大量合理场景。但匿名状态变量($、@、%)适用场景更窄,最大闪光点就是写单行脚本。
之所以可以这样写,是因为代码片段运行在持久作用域,匿名状态变量 $ 的自增前置表达式 ++$ 才可以保存状态。
匿名数组 @、匿名哈希 % 多用于列表处理。比如用 % 做简单分类统计,实时输出分类结果。注意:要拿最终结果,看最后一行输出,它才是哈希最终状态。
举个例子:我们想要对错误日志分类,搞清楚代码哪些模块抛出哪一类警告。
日志行样例:
plaintext
ERROR__Example.class__Exception text goes here处理命令:
fish
echo -e "ERROR__Example.class__Exception text goes here\nERROR__Example.class__A second exception" | raku -ne 'next unless .starts-with("ERROR"); my ($class, $exception-text) = .split("__")[1,*-1]; dd (%).push($class, $exception-text)'输出:
plaintext
{"Example.class" => "Exception text goes here"}
{"Example.class" => $["Exception text goes here", "A second exception"]}匿名状态变量有个关键点:同一块代码里,每出现一次 $ / @ / %,就是全新的独立匿名状态变量,不能复用同一个。
raku
{ [$++, --$] } # 这个块里面是两个完全独立的匿名状态变量文件截断不会重置匿名状态变量
行计数代码有一个值得注意的坑:日志文件被截断(日志轮转、删除重建都会发生),但 Raku 进程不会重启,计数器会继续累加,不会归零。
如何处理这个行为,留给读者自行实践。
博客文章很难做到把 Raku 讲得面面俱到
我们上面深入聊了匿名状态变量,虽然有点钻细节,但值得讲清楚。本篇属于《Shelling in Raku》系列,面向完全不了解 Raku 的读者。面向这类读者是一件很有挑战的事:Raku 的语法、语义处处都是精心设计,哪怕短短的示例代码,也藏着一大堆值得展开讲的特性。
举个例子,细心读者会看到 *-1,用来取数组最后一个元素。数组下标可以传入可调用对象,数组会把自身长度(@array.elems 或者 +@array)传给它。想要完整解释,就得深挖 WhateverCode(* 在 Raku 叫 Whatever)以及 “预填充表达式” 概念。这些概念本身不难,实际上手也容易理解。但仅仅为了取数组最后一个元素,就要长篇大论讲解这套机制,体验并不友好。
另一方面,熟练 Raku 用户不需要这些铺垫解释。于是很多优秀 Raku 博客,花大量篇幅解释示例里用到的部分语言特性,但又没法全部讲透。
平衡点很难把握。哪怕本系列目标不是资深开发者,我依然要权衡哪些该讲,讲到什么程度。真心希望,遇到看不懂的语法,会激励你进一步学习 Raku,而不是直接劝退。
评论