Shelling in Raku: -ne is unequaled for log lines
The transition of raku-intellij-plugin to Intellij IDEA 2026.1 started out pretty rough.
For some reason, user upgrades of IDEA always breaks compatibility with rip even though as far as I can tell I did everything I was supposed to in order to deliver an open-ended approach ("if it don't break...").
I ended up monitoring a test suite that took over an hour to complete. (We've since got it down to 2.5 minutes or less, but that's another story). Tailing the test log was unpleasant due to way too much noise and also test file names buried in the wall of glyphs.
So I took to Raku and was so happy with the solution that I wanted to share it with you.
The trick is in -n
➤ 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/'
This says that for every line of output from tail -f $LOG, run the argument to -e.
The other character-saving magic is in the near-mystical usability of topic variables in Raku.
.sayis called on the in-lexical-scope topic variable when the grammar sees that the method is a bare call without antecedent.- The post-fix conditional
ifis provided bare regexes, which are also automatically called against the in-lexical-scope topic variable. Introducingnothere works exactly as expected.
Who woulda thunk it?
These bare regexes are what we call "thunkable" expressions. This can be a bit of a tricky corner to catch the syntax of, but there are essentially places we expect the user to prefer terseness and in those places we wrap an acceptably thunkable expression with a code block and call it with (usually, but not always) the topic variable (whose full name is always $_).
Putting some processing on it
➤ tail -f $LOG | raku -ne '(++$, .split(" ")[0,1,*-1]).join(" ").say if /"exit dumb mode"/ && not /light_temp/'
I'll admit, when I first saw ++$, my mind had a lot of trouble parsing it as a reasonable syntax. Once you get a handle on these anonymous state variables, however, I expect you will miss them when you they aren't readily accessible (read: as in every other language I've ever used).
In our case we are able to create a counter for our refined test output without any more ceremony than applying a ++ to a $, eg. incrementing an anonymous state variable. State variables are awesome and have lots of legitimate use cases. The use cases for the anonymous state variable ($, @, and %) are all more narrow. They all shine their brightest in one-liners.
This is possible because the code snippet is run within a persistent scope, which is what allows the use of the anonymous state variable $ in a pre-assign increment expression (++$).
For the latter two, they are most often used in conjunction with list processing applications. For example you can use % to do a simple classification. In this example, you get a running account of the classification. This means the you'd want to use the final output line as it represents the final state of our % data structure.
Let's imagine that we want to classify errors so that we know which parts of our code generate what types of warnings.
Given a log line like so:
ERROR__Example.class__Exception text goes here
Process it with:
➤ 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)'
{"Example.class" => "Exception text goes here"}
{"Example.class" => $["Exception text goes here", "A second exception"]}
The thing with anonymous state variables is that they can never be directly referenced twice. In other words, each instance of $ or @ or % in a block of code refers to an entirely new anonymous state variable.
{ [$++, --$] } # A block containing two separate anonymous state
# variables
File truncation does not reset the anonymous state variable
The line counting code has one issue worth noting: truncating the log file (which happens when log files are rotated or deleted and recreated) does not restart the Raku process, which means the counter will keep counting.
Addressing this behavior is left as an exercise for the reader.
A note on the impossibility of genuinely comprehensive explanations in blog posts about Raku
We got out into the weeds a bit with anonymous state variables but I think it was worth going into. The Shelling in Raku series is intended for an audience that doesn't know Raku at all. Targeting this audience can be a daunting task as there are so many carefully considered pieces of Raku's syntax and semantics that even short examples like we have in this article will contain something that deserves further elaboration.
As an example, the eagle-eyed among you may have noticed that *-1 is used to access the last element of an array. The array index code can take a Callable, to which it passes its size (pronounced as either @array.elems or +@array). But to fully explain, I would need to dive into WhateverCode syntax (* is pronounced Whatever in Raku) and the concept of "primed" expressions. These are important and relatively simple to hold properly in practice. But not being able to index the last element of an array in an example without hosting a monologue on primed expressions can become a bummer.
Meanwhile, experienced Raku users do not require any of these explanations and thus a lot of great Raku blog posts are bogged down with explaining some (but rarely all) of the language features used in their examples.
So it is a tricky line to walk. Even in series like Shelling in Raku where expert users are not the intended audience, I must still weigh the merits of what to explain and of how much. It is my sincere hope that you find any remaining syntax questions as the start of a dive into Raku, rather than a reason to bounce off and away.