Perl / Unix 单行脚本对决,第二部分

在第一部分中,我比较了Perl的正则表达式功能与sed和Awk。在本节的最后一部分,我将涵盖利用Perl丰富的内置功能和第三方模块的示例。
更大的库
与Awk相比,Perl拥有更多的内置函数。对于命令行使用,我经常需要tr
、join
、map
和grep
。我喜欢Perl中的数组与哈希是区分开的,并且对这些数据类型应用sort
要简单得多。
向列表中添加项目
这个问题希望向列数不足的行添加列,如b
、c
和d
行
a,10,12,13
b,20,22
c,30
d,33
这个例子通过再次使用/e
将零添加到列表中。这次,Perl在替换中计算逗号的数量,并将其从3中减去,以找出还需要多少列。其中x
是字符串复制运算符
$ perl -pe 's|$|",0" x (3 - tr/,//)|e' ip.txt
a,10,12,13
b,20,22,0
c,30,0,0
d,33,0,0
颠倒事物
在针对特定字段的DNA序列的反向互补中,我需要选择字符串的一部分,进行互补,并将其颠倒。我想处理第三列
ABC DEF GATTAG GHK
ABC DEF GGCGTC GHK
ABC DEF AATTCC GHK
我在替换的一侧使用了tr
和reverse
(再次使用/e
)
$ perl -pe 's/^(\H+\h+){2}\K\H+/reverse $&=~tr|ATGC|TACG|r/e' test.txt
ABC DEF CTAATC GHK
ABC DEF GACGCC GHK
ABC DEF GGAATT GHK
或者,我可以使用-a
,它会自动根据空白分割结果,并将结果放入@F
中。然后我处理第三元素,再次输出@F
$ perl -lane '$F[2]=reverse $F[2]=~tr/ATGC/TACG/r; print "@F"' test.txt
ABC DEF CTAATC GHK
ABC DEF GACGCC GHK
ABC DEF GGAATT GHK
对CSV行进行排序
关于在不包含标题和第一列的情况下对csv文件中的行进行排序?这里有一些简单的以逗号分隔的值
id,h1,h2,h3,h4,h5,h6,h7
101,zebra,1,papa,4,dog,3,apple
102,2,yahoo,5,kangaroo,7,ape
我又使用了-a
,但还使用了-F,
,使逗号成为字段分隔符
$ perl -F, -lane 'print join ",", $.==1 ? @F : (shift @F, sort @F)' ip.txt
id,h1,h2,h3,h4,h5,h6,h7
101,1,3,4,apple,dog,papa,zebra
102,2,5,7,ape,kangaroo,yahoo
$.
变量跟踪输入行号。我使用这个变量跳过第一行(标题)。在其他所有行中,我制作一个包含@F
第一个元素的列表和其余元素的排序列表。注意,本例中要排序的数字位数相同,否则不会起作用。
插入增量行和列标签
在矩阵中插入行和列需要添加固定间隔的数值标签
2 3 4 1 2 3
3 4 5 2 4 6
2 4 0 5 0 7
0 0 5 6 3 8
在这里,我使用map
生成标题
$ perl -lane 'print join "\t", "", map {20.00+$_*0.33} 0..$#F if $.==1;
print join "\t", 100+(0.33*$i++), @F' matrix.txt
20 20.33 20.66 20.99 21.32 21.65
100 2 3 4 1 2 3
100.33 3 4 5 2 4 6
100.66 2 4 0 5 0 7
100.99 0 0 5 6 3 8
# with formatting and alternate way to join print arguments
$ perl -lane 'BEGIN{$,="\t"; $st=0.33}
print "", map {sprintf "%.2f", 20+$_*$st} 0..$#F if $.==1;
print sprintf("%.2f", 100+($st*$i++)), @F' matrix.txt
20.00 20.33 20.66 20.99 21.32 21.65
100.00 2 3 4 1 2 3
100.33 3 4 5 2 4 6
100.66 2 4 0 5 0 7
100.99 0 0 5 6 3 8
使用Perl模块
除了内置函数外,标准或CPAN模块也非常有用。使用-M
加载它们,并在=
之后放置导入列表
# randomize word list after filtering
$ s='floor bat to dubious four pact feed'
$ echo "$s" | perl -MList::Util=shuffle -lanE '
say join ":", shuffle grep {/[au]/} @F'
bat:four:pact:dubious
# remove duplicate elements while retaining input order
$ s='3,b,a,3,c,d,1,d,c,2,2,2,3,1,b'
$ echo "$s" | perl -MList::Util=uniq -F, -lanE 'say join ",", uniq @F'
3,b,a,c,d,1,2
# apply base64 decoding only for a portion of the string
$ s='123 aGVsbG8gd29ybGQK'
$ echo "$s" | perl -MMIME::Base64 -ane 'print decode_base64 $F[1]'
hello world
CPAN
综合Perl档案网络(CPAN)有各种用例的模块的大量集合。这里有一些示例。
提取IPv4地址
Regexp::Common有匹配常见事物的配方。这里有一些包含点分十进制IP地址的文本
3.5.52.243 555.4.3.1 34242534.23.42.42
foo 234.233.54.123 baz 4.4.4.3123
提取IPv4地址非常简单
$ perl -MRegexp::Common=net -nE 'say $& while /$RE{net}{IPv4}/g' ipv4.txt
3.5.52.243
55.4.3.1
34.23.42.42
234.233.54.123
4.4.4.31
只有当IPv4地址不被数字字符包围时才能匹配,因此我不会在34242534.23.42.42
的中间匹配
$ perl -MRegexp::Common=net -nE '
say $& while /(?<!\d)$RE{net}{IPv4}(?!\d)/g' ipv4.txt
3.5.52.243
234.233.54.123
真正的CSV处理
之前我进行了一些简单的CSV处理,但如果你想真正进行CSV处理,可以使用Text::CSV_XS来确保一切正确发生。这个处理了引用字段fox,42
$ s='eagle,"fox,42",bee,frog\n1,2,3,4'
# note that neither -n nor -p are used here
$ printf '%b' "$s" | perl -MText::CSV_XS -E 'say $row->[1]
while $row = Text::CSV_XS->new->getline(*ARGV)'
fox,42
2
处理XML
处理XML文件是另一种容易出错的格式。许多人尝试使用正则表达式来做这件事,但那很容易出错。这里是一个示例文件
<doc>
<greeting type="ask">Hi there. How are you?</greeting>
<greeting type="reply">I am good.</greeting>
<color>
<blue>flower</blue>
<blue>sand stone</blue>
<light-blue>sky</light-blue>
<light-blue>water</light-blue>
</color>
</doc>
可以使用xpath
(一个Perl程序)和xmllint
来处理XML文件
$ xpath -e '//blue/text()' sample.xml
Found 2 nodes in sample.xml:
-- NODE --
flower
-- NODE --
sand stone
$ xpath -q -e '//blue/text()' sample.xml
flower
sand stone
$ xmllint --xpath '//blue/text()' sample.xml
flower
sand stone
如果你需要Perl的强大功能,使用XML::LibXML模块会有所帮助
$ perl -MXML::LibXML -E '
$ip = XML::LibXML->load_xml(location => $ARGV[0]);
say $_->to_literal() for $ip->findnodes("//blue")' sample.xml
flower
sand stone
$ perl -MXML::LibXML -E '
$ip = XML::LibXML->load_xml(location => $ARGV[0]);
say uc $_->to_literal() for $ip->findnodes("//blue")' sample.xml
FLOWER
SAND STONE
处理 JSON
JSON 文件也存在同样的问题。您不希望对此进行正则表达式处理
$ s='{"greeting":"hi","marks":[78,62,93],"fruit":"apple"}'
各种 JSON 模块,例如 Cpanel::JSON::XS 可以处理这个问题。例如,格式化打印
$ echo "$s" | cpanel_json_xs
{
"fruit" : "apple",
"greeting" : "hi",
"marks" : [
78,
62,
93
]
}
这里是特别选择的内容
$ echo "$s" | perl -MCpanel::JSON::XS -0777 -E '$ip=decode_json <>;
say join ":", @{$ip->{marks}}'
78:62:93
有时将其放在脚本中会更简单(尽管这不再是真正的单行脚本)。我使用 Bash 函数作为快捷方式
$ pj() { perl -MCpanel::JSON::XS -0777 -E '$ip=decode_json <>;'"$@" ; }
$ echo "$s" | pj 'say $ip->{fruit}'
apple
$ echo "$s" | pj 'say join ":", @{$ip->{marks}}'
78:62:93
相同的非 Perl 示例是 jq,但这是需要单独安装的东西,可能不可用
$ echo "$s" | jq '.fruit'
"apple"
$ echo "$s" | jq '.marks | join(":")'
"78:62:93"
速度
与专用工具相比,Perl 通常较慢,但正则表达式引擎在某些回溯和量词的情况下表现更好。
$ time LC_ALL=C grep -xE '([a-z]..)\1' /usr/share/dict/words > f1
real 0m0.074s
$ time perl -ne 'print if /^([a-z]..)\1$/' /usr/share/dict/words > f2
real 0m0.024s
$ time LC_ALL=C grep -xP '([a-z]..)\1' /usr/share/dict/words > f3
real 0m0.010s
与 Awk 的关联数组相比,Perl 的哈希实现对于大量键的性能更好。下面的 SCOWL-wl.txt
文件是使用 app.aspell.net 创建的。 words.txt
来自 /usr/share/dict/words
。Mawk 通常更快,但 GNU Awk 在这个特定情况下做得更好。
$ wc -l words.txt SCOWL-wl.txt
99171 words.txt
662349 SCOWL-wl.txt
761520 total
# finding common lines between two files
# Case 1: shorter file passed as the first argument
$ time mawk 'NR==FNR{a[$0]; next} $0 in a' words.txt SCOWL-wl.txt > t1
real 0m0.296s
$ time gawk 'NR==FNR{a[$0]; next} $0 in a' words.txt SCOWL-wl.txt > t2
real 0m0.210s
$ time perl -ne 'if(!$#ARGV){$h{$_}=1; next}
print if exists $h{$_}' words.txt SCOWL-wl.txt > t3
real 0m0.189s
# Case 2: longer file passed as the first argument
$ time mawk 'NR==FNR{a[$0]; next} $0 in a' SCOWL-wl.txt words.txt > f1
real 0m0.539s
$ time gawk 'NR==FNR{a[$0]; next} $0 in a' SCOWL-wl.txt words.txt > f2
real 0m0.380s
$ time perl -ne 'if(!$#ARGV){$h{$_}=1; next}
print if exists $h{$_}' SCOWL-wl.txt words.txt > f3
real 0m0.351s
其他阅读内容
我的关于 Perl 单行脚本 的电子书
Awesome Perl 包含了精选的 Perl5 框架、库和软件列表
Perl XML::LibXML 例子,一本关于
XML::LibXML
模块的详细书籍
[图片来自 Riccardo Maria Mantero 在 Flickr 上的照片,(CC BY-NC-ND 2.0)]
标签
反馈
这篇文章有什么问题?请在 GitHub 上打开一个问题或拉取请求,帮助我们