tailgrep
跟随一个文件, 过滤行, 观察 spinner.
我有时会使用 tail -f
来观察文件,还可以使用 grep 输出特定的短语。
$ tail -f /var/log/nginx/access.log | grep special
1.2.3.4 - - [29/Aug/2018 -0400] "GET /special"…
█
例如,我可能会在 nginx 日志文件中查找 URL。我可能会启动一个屏幕会话,将输出结果发送到日志中,然后在一个长时间运行的命令中过滤短语。或者使用我最近了解到的脚本来生成 shell 会话的实时日志。
$ screen -L
$ script -F /tmp/out
$ echo "Read me back the last line"
$ tail -f screenlog.0 | grep Read
Read me back the last line
█
在一个完美的世界里,这就是我所需要的一切。但有时事情会出错。 Web 服务器停止了。长时间运行的命令被杀死。而 tail -f | grep
并不能帮我意识到输出已经停止。
$ tail -f /tmp/out | grep Read
Read me back the last line
█
如果能有一个旋转器来显示输出仍在产生, 那就更好了。
如果在没有输出的情况下,旋转器停止旋转也会很好。
/
所以,这是有一个小程序来实现这一目标。我把它叫做 tailgrep
。
我没有重新实现 tail - 我懒得考虑打开并搜索文件的结尾 - 我只是制作了一个 Proc::Async 对象然后看标准输出。
有一些微妙之处 - whenever 看起来像一个循环, 但不是。它设置了回调。而 react 声明了一个事件循环。 start 把它放在一个单独的线程中。
另外,你还得把顺序搞对。先创建 Proc::Async 对象,然后调用 .stdout
来创建一个 Supply。使用 whenever 在事件循环上设置回调。然后启动(start)这个过程。
它返回一个你可以等待(await)的 Promise。
还有什么。
shell "tput 'civis'";
和 shell "tput 'cnorm'";
使光标不可见(或可见)。 signal(SIGINT)是每当按下 ^C
时调用的另一个 Supply(即接收到 sigint 信号)。
< >
将字符串拆分为数组。 << ... >>
进行插值。 $++
维护一个状态变量并(后)增加它。
#!/usr/bin/env raku
sub spinner() {
<\ - | - / ->[$++ % 6]
}
sub MAIN($expr, $filename) {
shell "tput 'civis'";
my $proc = Proc::Async.new:
<<tail -f $filename>>;
my $out = $proc.stdout;
start react {
whenever $out.lines.grep( / "$expr" / ) {
.say
}
whenever $out.lines {
print spinner() ~ "\r";
}
whenever signal(SIGINT) {
shell 'tput cnorm';
exit;
}
}
await $proc.start;
}
还有更好的:
$ tailgrep special /var/log/nginx/access.log
1.2.3.4 - - [29/Aug/2018 -0400] "GET /special"…
哦,对了,MAIN($expr,$filename) 声明了参数和命令行参数,并且还生成一个使用信息,当你使用 -h 添加启动程序时, 这个信息就会显示出来。
$ ./tailgrep -h
Usage:
./tailgrep <expr> <filename>
对于 25 行代码来说还不错,但让我们再多做一点。
首先,我想确保文件已存在。要做到这一点,我在 $filename
参数上添加了一个约束。
sub MAIN($expr, $filename where *.IO.e) {
如果几秒钟内没有输出,我们也写一条消息。
我们再做一个 Supply
, 每秒钟调用一个小的例程来检查。
my $last-seen = DateTime.now;
...
whenever $out.lines {
print spinner() ~ "\r";
$last-seen = DateTime.now;
}
whenever Supply.interval(1) {
if DateTime.now - $last-seen > $wait {
say "--no lines for $wait seconds--";
}
}
我们所说的"几秒钟"指的是命令行上给出的任何数值,但是默认为 2。
sub MAIN(
$expr, #= what to search for
$filename where *.IO.e, #= a filename to grep
Numeric :$wait = 2, #= when to notify
) {
我有没有说过, 附加在参数上的内联注释会成为帮助输出中的消息?
或者说命名的参数(以 :
开头的参数)会变成命名的命令行参数?
./tailgrep -h
Usage:
./tailgrep [--wait=<Numeric>] <expr> <filename>
<expr> what to search for
<filename> a filename to grep
--wait=<Numeric> when to notify
这就是我的想法,但为了好玩,我还决定寻找一些更好的 unicode spinners,并添加一些颜色。对于这些功能,我需要使用一些可能需要单独安装的模块。
zef install JSON::Fast Terminal::ANSIColor
所以,我在这里找到了一些 spinners,带有一个很好的 JSON 文件,所以我们只需下载它并将其存储在本地。
#!/usr/bin/e nü
use JSON::Fast;
use Terminal::ANSIColor;
my @frames = < / - | - \ - >;
sub spinner {
@frames[$++ % +@frames];
}
sub download-spinners {
my $store = "{ %*ENV<HOME> }/.spinners.json";
unless $store.IO.e {
say "downloading spinners";
my $url='https://raw.githubusercontent.com'
~ '/sindresorhus/cli-spinners'
~ '/HEAD/spinners.json';
shell "curl -s $url > $store";
}
from-json( $store.IO.slurp );
}
另外多重分派也很方便 - 另一个分派候选者的命名布尔参数可以显示所有 spinners 的列表。
multi MAIN(
Bool :$list-spinners!, #= list spinners
) {
say download-spinners.keys.sort.join("\t");
}
...
这里是带有花哨的 unicode spinners 的最终程序。这里 是一个不那么花哨的程序(没有依赖)。
如果你想尝试一下,这里有一些方便的网址供 curling。
curl -L https://git.io/fA8MV > ~/bin/tg-fancy
curl -L https://git.io/fA8MM > ~/bin/tailgrep
执行 chmod +x
, 完了。
结论
- 混合和匹配你的 CLI 实用工具以获得乐趣,以改善你的生活质量。
- 线程、Promise 和 Supplies 是异步编程的方便工具。
- Spinners 不只是用于烦躁不安。有时他们很有用。