Raku 中的单行程序
— 焉知非鱼One Liners in Raku
Raku 单行程序
这本书在进行之中。我希望你能觉得它有趣,甚至可能有用!如果你想贡献反馈的话,那么很欢迎提问题还有新的或有提升的正则表达式。
作者 #
戴维法瑞尔 PerlTricks.com
版本 #
版本 0.01
许可 #
FreeBSD
贡献者 #
- Alexander Moquin
- Bruce Gray
- Carl Mäsak
- David H. Adler
- FROGGS
- Helmut Wollmersdorfer
- japhb
- Larry Wall
- Matt Oates
- Moritz Lenz
- Mouq
- Salve J Nilsen
- Sam S
- Skids
- timotimo
致谢 #
启发于 Peteris Krumins 的 Perl 5 example 文件。他逐字逐句地写了一本关于 Perl 5 单行 的书。
irc上有很好地 folks。
内容 #
- 介绍
- 教程
- 文件间距
- 行号
- 计算
- 创建字符串和创建数组
- 文本转换和替换
- 文本分析
- 选择性的行打印
- 使用管道转换数据(进行中)
- WWW(进行中)
- 转换到 Windows
介绍 #
把 Perl 和其它语言区别开的一件事情是在单行代码中写小程序的能力,即人们所熟知的"单行"。在终端里直接键入一个程序比写一个废弃的脚本往往更快。并且单行程序也很强大;它们是羽翼丰满的程序,能够加载外部库,但是也能集成到终端中。你可以在单行程序中输入或输出数据。
像 Perl 5 一样, Raku支持单行程序。还有就像 Raku 到处清理着 Perl 5 的毒瘤一样,Raku 的单行语法也更好了。它拥有更少的特殊变量和选项因此更容易记忆。这本书提供了很多有用的 Raku 单行例子,从找出文件中得重复行到运行一个 web 服务器,它几乎能做所有事情。尽管 Raku 拥有更少的特殊变量,但是由于它高级的面向对象的语法,Raku 中的大部分单行程序比等价的 Perl 5 单行程序更短。
这本书可以以多种方式阅读。如果你是单行程序的新手,从教程开始。它带领你掌握单行程序的核心概念;不要担心,一旦你理解了它会很容易。如果你精通 Perl,Bash,或 Sed/Awk,你可以立马开始工作。随意跳过和浏览你所感兴趣的东西。如果有些代码你不理解,那么在终端中试试!这个仓库中包含的无处不在的文件是 example.txt,它会在很多单行程序中用到。
使用单行编程仅仅是 Raku 擅长的一个范例。这样的代码小而美,但是同时你正学习的是一种生产力技能,记住你正在学的是一种新的编程语言。检查 raku.org 网站获取官方文档。
教程 #
要开始单行程序,所有你要掌握的是 -e 选项。这告诉 Perl 把它后面所跟的东西作为一个程序去执行。例如:
raku -e 'say "Hello, World!"'
我们来一步步剖析这段代码。 raku
引用了 Raku 程序, -e
告诉 Raku 去执行,而 'say "Hello, World!"'
是要执行的程序。每个程序都必须被包围在单引号中(除了在 Windows 上,查看 转换到 Windows)。要运行单行程序,就把它键入到终端中好了:
> raku -e 'say "Hello, World!"'
Hello, World!
如果你想加载一个文件,就把文件路径添加到程序代码的后面:
raku -e 'for (lines) {say $_}' /path/to/file.txt
这个程序打印出了 path/to/file.txt
的每一行。你可能知道 $_
是默认变量,它在这儿是指正被循环的当前行。lines
是一个列表,当你传递一个文件路径给单行程序的时候会自动为你创建这个列表。现在我们来重写那个单行程序,一步一步。它们都是等价的:
raku -e 'for (lines) { say $_ }' /path/to/file.txt
raku -e 'for (lines) { $_.say }' /path/to/file.txt
raku -e 'for (lines) { .say }' /path/to/file.txt
raku -e '.say for (lines)' /path/to/file.txt
raku -e '.say for lines' /path/to/file.txt
就像 $_
是默认变量一样,在默认变量身上调用的方法可以省略掉变量引用。它们变成了默认方法。所以 $_.say
变成 .say
。回报给写单行程序的人的东西是 - 更少的键入!
-n
选项改变了程序的行为:它为文件中的每一行执行一次代码。所以,大写并打印 path/to/file.txt
的每一行你会键入:
raku -ne '.uc.say' /path/to/file.txt
-p
选项就像 -n
, 除了它会自动打印 $_
之外。所以大写文件中的所有行的另外一种方法是:
raku -pe '$_ = .uc' /path/to/file.txt
或者两个做同样事情的更短的版本:
raku -pe '.=uc' /path/to/file.txt
raku -pe .=uc /path/to/file.txt
在第二个例子中,我们可以完全移除周围的单引号。这种场景很少遇到,但是如果你的单行程序中没有空格并且没有符号或引号,那么你通常可以移除外部的引号。
-n
和 -p
选项真的很有用。本书中也有很多使用它们的单行例子。
最后一件你要知道的事情是怎么加载模块。 -M
开关代表着加载模块:
raku -M URI::Encode -e 'say encode_uri("example.com/10 ways to crush it with Raku")'
-M URI::Encode
加载了 URI::Encode
模块,它导入了 encode_uri 子例程。 你可以多次使用 -M
来加载多个模块:
raku -M URI::Encode -M URI -e '<your code here>'
如果你有一个还没有安装的本地模块呢? 简单, 仅仅传递一个 -I
开关来包含那个目录好了:
raku -I lib -M URI::Encode -e '<your code here>'
现在 Raku 会在 lib
目录中搜索 URI::Encode
模块,和标准的安装位置一样。
要查看 Raku 命令行开关有哪些, 使用 -h
选项查看帮助:
raku -h
这打印可获得的不错的统计。
文件间距 #
Double space a file
raku -pe '$_ ~= "\n"' example.txt
N-space a file (例如. 4倍空白)
raku -pe '$_ ~= "\n" x 4' example.txt
在每一行前面添加一个空行:
raku -pe 'say ""' example.txt
移除所有空行:
raku -ne '.say if /\S/' example.txt
raku -ne '.say if .chars' example.txt
移除所有的连续空白行,只保留一行:
raku -e '$*ARGFILES.slurp.subst(/\n+/, "\n\n", :g).say' example.txt
行号 #
给文件中的所有行编号:
raku -ne 'say "{++$} $_"' example.txt
raku -ne 'say $*ARGFILES.ins ~ " $_ "' example.txt
只给文件中得非空行编号:
raku -pe '$_ = "{++$} $_" if /\S/' example.txt
给所有行编号但是只打印非空行:
raku -pe '$_ = $*ARGFILES.ins ~ " $_ " if /\S/' example.txt
打印文件中行数的总数:
raku -e 'say lines.elems' example.txt
raku -e 'say lines.Int' example.txt
raku -e 'lines.Int.say' example.txt
打印出文件中非空行的总数:
raku -e 'lines.grep(/\S/.elems.say)' example.txt
打印文件中空行的数量:
raku -e 'lines.grep(/^\s*$/).elems.say' example.txt
计算 #
检查一个数是否是质数:
raku -e 'say "7 is prime" if 7.is-prime'
打印一行中所有字段的和:
raku -ne 'say [+] .split("\t")'
打印所有行的所有字段的和:
raku -e 'say [+] lines.split("\t")'
打乱行中的所有字段:
raku -ne '.split("\t").pick(*).join("\t").say'
找出一行中最小的元素:
raku -ne '.split("\t").min.say'
找出所有行的最小的元素:
raku -e 'lines.split("\t").min.say'
找出一行中最大的元素:
raku -ne '.split("\t").max.say'
找出所有行的最大的元素:
raku -e 'lines.split("\t").max.say'
找出一行中得数值化最小元素:
raku -ne '.split("\t")».Numeric.min.say'
找出一行中得数值化最大元素:
raku -ne '.split("\t")».Numeric.max.say'
使用字段的绝对值替换每个字段:
raku -ne '.split("\t").map(*.abs).join("\t")'
找出每行中字符的总数:
raku -ne '.chars.say' example.txt
找出每行中单词的总数:
raku -ne '.words.elems.say' example.txt
找出每行中由逗号分隔的元素的总数:
raku -ne '.split(",").elems.say' example.txt
找出所有行的字段(单词)的总数:
raku -e 'say lines.split("\t").elems' example.txt # fields
raku -e 'say lines.words.elems' example.txt # words
打印匹配某个模式的字段的总数:
raku -e 'say lines.split("\t").comb(/pattern/).elems' example.txt # fields
raku -e 'say lines.words.comb(/pattern/).elems' example.txt # words
打印匹配某个模式的行的总数:
raku -e 'say lines.grep(/in/.elems)' example.txt
打印数字 PI 到 n 位小数点(例如. 10位):
raku -e 'say pi.fmt("%.10f");'
打印数字 PI 到 15 位小数点:
raku -e 'say π'
打印数字 E 到 n 位小数点(例如. 10位):
raku -e 'say e.fmt("%.10f");'
打印数字 E 到 15 位小数点:
raku -e 'say e'
打印 UNIX 时间 (seconds since Jan 1, 1970, 00:00:00 UTC)
raku -e 'say time'
打印 GMT (格林威治标准时间)和地方计算机时间:
raku -MDateTime::TimeZone -e 'say to-timezone("GMT",DateTime.now)'
raku -e 'say DateTime.now'
以 H:M:S 格式打印当地计算机时间:
raku -e 'say DateTime.now.map({$_.hour, $_.minute, $_.second.round}).join(":")'
打印昨天的日期:
raku -e 'say DateTime.now.earlier(:1day)'
打印日期: 14 个月, 9 天,和 7 秒之前
raku -e 'say DateTime.now.earlier(:14months).earlier(:9days).earlier(:7seconds)'
在标准输出前加上时间戳(GMT,地方时间):
tail -f logfile | raku -MDateTime::TimeZone -ne 'say to-timezone("GMT",DateTime.now) ~ "\t$_"'
tail -f logfile | raku -ne 'say DateTime.now ~ "\t$_"'
计算 5 的阶乘:
raku -e 'say [*] 1..5'
计算最大公约数:
raku -e 'say [gcd] @list_of_numbers'
使用欧几里得算法计算数字 20 和 35 的最大公约数:
raku -e 'say (20, 35, *%* ... 0)[*-2]'
计算 20 和 35 的最小公倍数:
raku -e 'say 20 lcm 35'
使用欧几里得算法: n*m/gcd(n,m) 计算数字 20 和 35 的最小公倍数:
raku -e 'say 20 * 35 / (20 gcd 35)'
生成 10 个 5 到 15(不包括 15)之间的随机数:
raku -e '.say for (5..^15).roll(10)'
找出并打印列表的全排列:
raku -e 'say .join for [1..5].permutations'
生成幂集
raku -e '.say for <1 2 3>.combinations'
把 IP 地址转换为无符号整数:
raku -e 'say :256["127.0.0.1".comb(/\d+/)]'
raku -e 'say +":256[{q/127.0.0.1/.subst(:g,/\./,q/,/)}]"'
raku -e 'say Buf.new(+«"127.0.0.1".split(".")).unpack("N")'
把无符号整数转换为 IP 地址:
raku -e 'say join ".", @(pack "N", 2130706433)'
raku -e 'say join ".", map { ((2130706433+>(8*$_))+&0xFF) }, (3...0)'
创建字符串和创建数组 #
生成并打印字母表:
raku -e '.say for "a".."z"'
生成并打印所有从 “a” 到 “zz” 的字符串:
raku -e '.say for "a".."zz"'
把整数转换为十六进制:
raku -e 'say 255.base(16)'
raku -e 'say sprintf("%x", 255)'
把整数打印为十六进制转换表:
raku -e 'say sprintf("%3i => %2x", $_, $_) for 0..255'
把整数编码为百分数:
raku -e 'say sprintf("%%%x", 255)'
生成一个随机的 10 个 a-z 字符长度的字符串:
raku -e 'print roll 10, "a".."z"'
raku -e 'print roll "a".."z": 10'
生成一个随机的 15 个 ASCII 字符长度的密码:
raku -e 'print roll 15, "0".."z"'
raku -e 'print roll "0".."z": 15'
创建一个指定长度的字符串:
raku -e 'print "a" x 50'
生成并打印从 1 到 100 数字为偶数的数组:
raku -e '(1..100).grep(* %% 2).say'
找出字符串的长度:
raku -e '"storm in a teacup".chars.say'
找出数组的元素个数:
raku -e 'my @letters = "a".."z"; @letters.Int.say'
文本转换和替换 #
对文件进行 ROT 13 加密:
raku -pe 'tr/A..Za..z/N..ZA..Mn..za..m/' example.txt
对字符串进行 Base64 编码:
raku -MMIME::Base64 -ne 'print MIME::Base64.encode-str($_)' example.txt
对字符串进行 Base64 解码:
raku -MMIME::Base64 -ne 'print MIME::Base64.decode-str($_)' base64.txt
对字符串进行 URL 转义:
raku -MURI::Encode -le 'say uri_encode($string)'
URL-unescape a string
raku -MURI::Encode -le 'say uri_decode($string)'
HTML-encode a string
raku -MHTML::Entity -e 'print encode-entities($string)'
HTML-decode a string
raku -MHTML::Entity -e 'print decode-entities($string)'
把所有文本转换为大写:
raku -pe '.=uc' example.txt
raku -ne 'say .uc' example.txt
把所有文本转换为小写:
raku -pe '.=lc' example.txt
raku -ne 'say .lc' example.txt
只把每行的第一个单词转换为大写:
raku -ne 'say s/(\w+){}/{$0.uc}/' example.txt
颠倒字母的大小写:
raku -pe 'tr/a..zA..Z/A..Za..z/' example.txt
raku -ne 'say tr/a..zA..Z/A..Za..z/.after' example.txt
对每行进行驼峰式大小写:
raku -ne 'say .wordcase' example.txt
在每行的开头去掉前置空白(空格、tabs):
raku -ne 'say .trim-leading' example.txt
从每行的末尾去掉结尾的空白(空格、tabs):
raku -ne 'say .trim-trailing' example.txt
从每行中去除行首和行尾的空白:
raku -ne 'say .trim' example.txt
把 UNIX 换行符转换为 DOS/Windows 换行符:
raku -ne 'print .subst(/\n/, "\r\n")' example.txt
把 DOS/Windows 换行符转换为 UNIX 换行符:
raku -ne 'print .subst(/\r\n/, "\n")' example.txt
把每行中所有的 “ut” 实体用 “foo” 替换掉:
raku -pe 's:g/ut/foo/' example.txt
把包含 “lorem” 的每行中所有的 “ut” 实体用 “foo” 替换掉:
raku -pe 's:g/ut/foo/ if /Lorem/' example.txt
把文件转换为 JSON 格式:
raku -M JSON::Tiny -e 'say to-json(lines)' example.txt
从文件的每一行中随机挑选 5 个单词:
raku -ne 'say .words.pick(5)' example.txt
文本分析 #
Print n-grams of a string
raku -e 'my $n=2; say "banana".comb.rotor($n,$n-1).map({[~] @$_})'
打印唯一的 n-grams
```bash
raku -e 'my $n=2; say "banana".comb.rotor($n,$n-1).map({[~] @$_}).Set.sort'
打印 n-grams 的出现次数:
raku -e 'my $n=2; say "banana".comb.rotor($n,$n-1).map({[~] @$_}).Bag.sort.join("\n")'
打印单词的出现次数(1-grams):
raku -e 'say lines[0].words.map({[~] @$_}).Bag.sort.join("\n")' example.txt
基于一组 1-grams 打印 Dice 相似系数:
raku -e 'my $a="banana".comb;my $b="anna".comb;say ($a (&) $b)/($a.Set + $b.Set)'
基于 1-grams 打印卡得杰相似系数:
raku -e 'my $a="banana".comb;my $b="anna".comb;say ($a (&) $b) / ($a (|) $b)'
基于 1-grams 打印重叠系数:
raku -e 'my $a="banana".comb;my $b="anna".comb;say ($a (&) $b)/($a.Set.elems,$b.Set.elems).min'
基于 1-grams 打印类似的余弦:
raku -e 'my $a="banana".comb;my $b="anna".comb;say ($a (&) $b)/($a.Set.elems.sqrt*$b.Set.elems.sqrt)'
# 上面的命令提示 Seq 已经被消费
raku -e 'my $a="banana".comb;my $b="anna".comb;say ($a.cache (&) $b.cache)/($a.cache.Set.elems.sqrt*$b.cache.Set.elems.sqrt)'
创建字符串中字符的索引并打印出来:
raku -e 'say {}.push: %("banana".comb.pairs).invert'
创建一行中单词的所以并打印出来:
raku -e '({}.push: %(lines[0].words.pairs).invert).sort.join("\n").say' example.txt
选择性的行打印 #
打印文件的第一行(模仿 head -1):
raku -ne '.say;exit' example.txt
raku -e 'lines[0].say' example.txt
raku -e 'lines.shift.say' example.txt
打印文件的前 10 行(模仿 head -10)
raku -pe 'exit if ++$ > 10' example.txt
raku -ne '.say if ++$ < 11' example.txt
打印文件的最后一行(模仿 tail -1):
raku -e 'lines.pop.say' example.txt
打印文件的最后 5 行(模仿 tail -5):
raku -e '.say for lines[*-5..*]' example.txt
只打印包含元音的行:
raku -ne '/<[aeiou]>/ && .print' example.txt
打印包含所有元音的行:
raku -ne '.say if .comb (>=) <a e i o u>' example.txt
raku -ne '.say if .comb ⊇ <a e i o u>' example.txt
打印字符数大于或等于 80 的行:
raku -ne '.print if .chars >= 80' example.txt
raku -ne '.chars >= 80 && .print' example.txt
只打印第二行:
raku -ne '.print if ++$ == 2' example.txt
打印除了第二行的所有行:
raku -pe 'next if ++$ == 2' example.txt
打印第一行到第三行之间的所有行:
raku -ne '.print if (1..3).any == ++$' example.txt
打印两个正则表达式之间(包含匹配那个正则表达式的行)的所有行:
raku -ne '.print if /^Lorem/../laborum\.$/' example.txt
打印最长的行的长度:
raku -e 'say lines.max.chars' example.txt
raku -ne 'state $l=0; $l = .chars if .chars > $l;END { $l.say }' example.txt
打印长度最长的行:
raku -e 'say lines.max' example.txt
raku -e 'my $l=""; for (lines) {$l = $_ if .chars > $l.chars};END { $l.say }' example.txt
打印包含数字的所有行:
raku -ne '.say if /\d/' example.txt
raku -e '.say for lines.grep(/\d/)' example.txt
raku -ne '/\d/ && .say' example.txt
raku -pe 'next if ! $_.match(/\d/)' example.txt
打印只包含数字的所有行:
raku -ne '.say if /^\d+$/' example.txt
raku -e '.say for lines.grep(/^\d+$/)' example.txt
raku -ne '/^\d+$/ && .say' example.txt
raku -pe 'next if ! $_.match(/^\d+$/)' example.txt
打印每个奇数行:
raku -ne '.say if ++$ % 2' example.txt
打印每个偶数行:
raku -ne '.say if ! (++$ % 2)' example.txt
打印所有重复的行:
raku -ne 'state %l;.say if ++%l{$_}==2' example.txt
打印唯一的行:
raku -ne 'state %l;.say if ++%l{$_}==1' example.txt
打印每一行中的第一个字段(单词)(模仿 cut -f 1 -d ' ‘)
raku -ne '.words[0].say' example.txt
使用管道转换数据 #
Raku程序直接集成到了命令行中。你可以使用 |
管道符号从单行程序中输出数据和输入数据到单行程序中。为了 从管道中输入数据, Raku 自动地把 STDIN 设置为 $*IN
。就像对文件那样,从管道输入的数据在单行中也能使用 -n
来进行循环迭代。从单行程序中输出数据就使用 print 或 say 好了。
在当前目录中对所有文件进行 JSON 编码:
ls | raku -M JSON::Tiny -e 'say to-json(lines)'
打印文件中的大约 5% 的随机样本行:
raku -ne '.say if 1.rand <= 0.05' /usr/share/dict/words
颜色转换, 从 HTML 到 RGB
echo "#ffff00" | raku -ne '.comb(/\w\w/).map({:16($_)}).say'
颜色转换, 从 RGB 到 HTML
echo "#ffff00" | raku -ne '.comb(/\w\w/).map({:16($_)}).say'
WWW #
下载一个页面:
raku -M HTTP::UserAgent -e 'say HTTP::UserAgent.new.get("google.com").content'
下载一个页面并剥离 HTML:
wget -O - "http://raku.org" | raku -ne 's:g/\<.+?\>//.say'
下载一个页面并剥离并解码 HTML:
wget -O - "http://raku.org" | raku -MHTML::Strip -ne 'strip_html($_).say'
开启一个简单地 web 服务器:
raku -M HTTP::Server::Simple -e 'HTTP::Server::Simple.new.run'
转换到 Windows #
一旦你知道了里面的门道之后那么在 Windows 上运行单行程序就是小草一碟。单行程序既可以在 cmd.exe 中运行,又可以在 Powershell 中运行。主要的规则是:用双引号替换掉外部的单引号,在单行程序的内部使用插值引用操作符 qq//
来把字符串括起来。对于非插值的引起,你可以使用单引号。我们来看几个例子。
这儿有一个打印时间的单行程序:
raku -e 'say DateTime.now'
要在 Windows 上运行,我们仅仅用双引号替换掉单引号好了:
raku -e "say DateTime.now"
这个单行程序给文件中每一行添加了一个换行符,使用了插值字符串:
raku -pe '$_ ~= "\n"' example.txt
在 Windows 上这应该写为:
raku -pe "$_ ~= qq/\n/" example.txt
这种情况下,我们想对换行符进行插值,并且不为该行字面地添加反斜线和字符"n",所以我们必须使用 qq。但是你通常也可以像这样在单行程序中使用单引号:
raku -e 'say "Hello, World!"'
在 Windows 上这应该写为:
raku -e "say 'hello, World!'"
简单地输出重定向工作起来像基于 Unix 系统那样。 这个单行程序使用 >
把 ASCII 字符索引表打印到一个文件中:
raku -e "say .chr ~ ' ' ~ $_ for 0..255" > ascii_codes.txt
在使用 >
的时候,如果文件不存在就会创建一个。如果文件确实存在,它会被重写。你可能更想追加到文件,使用 >>
代替。