perl一行式程序系列文章:Perl一行式html
$ perl -pe '$_ = "$. $_"' file.log $ perl -ne 'print "$. $n"' file.log
这里涉及了一个特殊变量$.
。shell
这个特殊变量表明的是当前处理行的行号。对于Perl的一行式来讲,经过<>
隐式打开的文件句柄默认不会关闭,因此若是参数中有多个文件,进入下一个文件时行号不会重置。express
例如:数组
$ cat a.txt aaa bbb $ cat b.txt ccc ddd # 行号不重置 $ perl -pe '$_ = "$. $_"' a.txt b.txt 1 aaa 2 bbb 3 ccc 4 ddd
若是想要每一个文件的行号都独立计算。可使用下面这种方式进行判断:遇到文件尾部,显式关闭文件。bash
$ perl -e ' while(<>){ print "$. $_" }continue{ close ARGV if eof }' a.txt b.txt 1 aaa 2 bbb 1 ccc 2 ddd
$.
是Perl自带的文件句柄上的行号计数器,读取的每一行都会计数。因此若是想要统计文件中的某些行的行号,使用自带的$.
是不可行的,只能本身实现行号计数器。函数
以下:工具
$ perl -pe '$_ = ++$x." $_" if /\S/' file.log
这里的逻辑是,只要行中有非空白字符,就自增一个变量的值。自增后的值和字符串进行串联,并赋值给$_
被-p输出。scala
由于要删除某些行不输出,因此不能使用-p选项,它会将全部行都输出,除非使用s///
来删除整行。能够考虑使用-n选项。code
$ perl -ne 'print ++$x." $_" if /\S/' file.log
例如输出文件中匹配"nologin"单词的行号,其它行照常输出。htm
$ perl -pe '$_ = "$. $_" if /nologin/' file.log root x 0 0 root /root /bin/bash 2 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 3 bin x 2 2 bin /bin /usr/sbin/nologin 4 sys x 3 3 sys /dev /usr/sbin/nologin sync x 4 65534 sync /bin /bin/sync
若是想要单独计数被匹配行的行号,能够本身写计数器。
$ perl -pe '$_ = ++$num." $_" if /nologin/' file.log root x 0 0 root /root /bin/bash 1 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 2 bin x 2 2 bin /bin /usr/sbin/nologin 3 sys x 3 3 sys /dev /usr/sbin/nologin sync x 4 65534 sync /bin /bin/sync
若是须要格式化输出,使得没有匹配的行也和带有行号的行对齐。能够进行多分支的赋值:
$ perl -pe ' $_ = do { if(/nologin/){ ++$num." $_" }else{ " $_" }}' file.log root x 0 0 root /root /bin/bash 1 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 2 bin x 2 2 bin /bin /usr/sbin/nologin 3 sys x 3 3 sys /dev /usr/sbin/nologin sync x 4 65534 sync /bin /bin/sync
或者3目逻辑运算:
$ perl -pe '$_ = /nologin/ ? ++$num." $_" : " $_"' file.log
更规范的格式化可使用printf来对齐,由于这里我使用-p选项,使用使用sprintf格式化字符串保存到$_
变量上。
$ perl -pe ' $_ = do { if(/nologin/){ sprintf("%-3s %s", ++$num, $_); }else{ sprintf("%-3s %s","", $_); }}' file.log root x 0 0 root /root /bin/bash 1 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 2 bin x 2 2 bin /bin /usr/sbin/nologin 3 sys x 3 3 sys /dev /usr/sbin/nologin sync x 4 65534 sync /bin /bin/sync
例如输出能匹配"nologin"的行以及它们的行号。
由于只输出某些匹配行,而不是全部行,因此不使用-p选项。
$ perl -ne 'print "$. $_" if /nologin/' file.log 2 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 3 bin x 2 2 bin /bin /usr/sbin/nologin 4 sys x 3 3 sys /dev /usr/sbin/nologin
若是匹配行的行号要独立计数,则不使用$.
,本身写个自增的计数器便可:
$ perl -ne 'print ++$num." $_" if /nologin/' file.log 1 daemon x 1 1 daemon /usr/sbin /usr/sbin/nologin 2 bin x 2 2 bin /bin /usr/sbin/nologin 3 sys x 3 3 sys /dev /usr/sbin/nologin
$ perl -lne 'END{print $.}' file.log 5
这里使用END语句块,表示执行完主逻辑代码后程序退出前执行的,由于这个示例中没有主逻辑代码,因此读取完全部行后就会执行END语句块。另外,这里的-l选项主要用来为print追加换行符。
上面的语句仅会输出行号,不会输出文件名,并且多个文件的时候只会输出总行数,而不是每一个文件单独统计。
还有其它实现方式,介绍两个:
# (1) perl -le 'print scalar(@tmp = <>)' file.log perl -le 'print ~~@tmp = <>' file.log # (2) perl -ne '}{print $.' file.log
上面的方式(1)没有使用-p和-n,因此本身在-e表达式中写<>
。而@tmp = <>
是让<>
以列表的方式一次性读取全部行,而后scalar强制转换其为标量上下文,因而获得行数量。它等价于scalar( () = <> )
,还等价于$num = () =<>
。
scalar()能够替换成~~
符号,它是两个比特位取反操做,等价于什么都不作,但它工做在标量上下文,因此能够用来转换上下文。
上面的方式(2)使用的是超乎想象的}{
,这不是Perl中的什么特殊符号,仅仅只是结合-n选项时的一个技巧。-n选项的代码逻辑以下:
while(<>){ ... -e expression here }
因此,-e中指定}{print $.
表示破坏原始的-n逻辑,使之变成下面的逻辑:
while(<>){ }{print $. }
这个格式化一下就是:
while(<>){} { print $. }
也就是说,while循环体内不作任何操做,直到<>
读取完成后,while结束,而后运行一次性语句块{print $.}
。因此,-ne }{xxx
等价于在END语句块中执行xxx
操做。
wc -l
会单独输出每一个文件的行数,并总计全部文件的行数。例如:
$ wc -l file.log 5 file.log $ wc -l file.log paragraph.log 5 file.log 18 paragraph.log 23 total
因此,这也可使用perl一行式程序来实现。由于这个逻辑中须要单独统计每一个文件,因此必须显式区分每一个文件。使用eof判断每一个文件的尾部便可。
$ perl -M'List::Util qw(sum)' -lne ' # 将@ARGV保存起来,以便后续可以按前后顺序获取全部文件名 BEGIN{ @files = @ARGV; } # 每一个文件处理完时,保存行和文件信息,并关闭文件以便重置行号计数器 if(eof){ # 将每一个文件的行数注册到一个hash结构中 # hash的key为当前处理的文件名 $line_filename{$ARGV} = $.; close ARGV; } END{ # 获取总行数以及总行数的字符长度,以便格式化对齐 $total_lines = sum values %line_filename; $longest = length $total_lines; # 输出每一个文件对应的行数及文件名,且按照@ARGV的顺序输出 foreach (@files){ printf "%${longest}d %s\n",$line_filename{$_},$_; } # 输出总行数 print "$total_lines total"; } ' file.log paragraph.log
这是遇到的第一个比较大的程序,这样的逻辑应该写成Perl脚本而不是一行式程序。不过这里的几个知识点很适合引入Perl一行式。
先分析下这段程序的逻辑:要统计总行数,且要输出每一个文件对应的行数,输出时还要进行格式化对齐,因此先将每一个文件对应的行数保存到一个hash结构中,最后在END语句块中计算总行数,并计算总行数有多少个字符以便肯定格式化对齐时的字符数量。
上面使用了-M
选项,它表示导入一个模块。此处所导入的模块是List::Util
模块,它是额外的列表(数组)工具模块,该模块中有很多处理列表的工具。例如这里使用qw(sum)
表示导入这个模块中的sum函数,用于对列表元素进行加总。若是不写qw(sum)
,那么在使用sum函数的时候,须要写完整的名称List::Util::sum @arr
。
"-l"选项的目的是给print函数追加换行符。
另外这里使用了BEGIN语句块来保存@ARGV
数组,虽然%line_filename
中也能取得全部的文件名,但hash结构中的元素是无序的,要保证文件名的顺序,只能使用数组(列表)来保存。再者,由于@ARGV
中的参数文件会随着<>
的读取而被剔除出@ARGV
,因此应该在BEGIN中对@ARGV
进行保存。
用计数器实现很是简单:
$ perl -lne '++$num if /\S/;END{print $num+0;}' paragraph.log
这里的逻辑很是简单。惟一须要注意的是$num+0
,由于文件多是空的,使得END语句块中的$num
变量仍处于未定义状态。加上一个+0
,能够保证它会输出数值格式,未定义时则输出0。
为了保证获得数值,可使用int函数进行转换:
$ perl -lne '++$num if /\S/;END{print int $num;}' paragraph.log
我准备在这里引入grep函数的简单用法。
Shell中有个grep命令能够用来匹配内容,在Perl中也有一个grep函数,它的简单工做方式能够类同于shell的grep命令,用于筛选列表中符合条件的元素,并将这些元素构成一个新的列表。好比能正则匹配的元素、操做后布尔真的元素。
因此,可使用grep函数来匹配非空白行:
$ perl -e '@lines = grep /\S/,<>;print "@lines"' paragraph.log
grep期待的是列表上下文,使得<>
一次性读完全部行造成一个列表,而后grep对这个列表的每一个元素进行筛选,只要是非空白行都放入一个新的列表。
那么要统计非空白行数就很是简单了,直接将grep的结果转换成标量上下文就能够。
$ perl -le 'print ~~grep /\S/,<>' paragraph.log
前面说过,~~
能够用来转换标量上下文。
其实Perl grep函数要强大的多,它支持完整的流程控制逻辑。若有须要,参考Perl grep函数。
为文件中每行中的单词进行计数。
$ perl -pe 's/(\w+)/"<".++$num.">.$1"/ge' file.log <1>.first <2>.paragraph: <3>.first <4>.line <5>.in <6>.1st <7>.paragraph <8>.second <9>.line <10>.in <11>.1st <12>.paragraph <13>.third <14>.line <15>.in <16>.1st <17>.paragraph
这里使用s///
命令的e修饰符。该修饰符能够评估s/reg/replacement/
的replacement部分,将其做为Perl的代码被perl执行,而后进行s替换操做。
正如上面的示例,每一行匹配后评估replacement部分是"<".++$num.">.$1"
,被perl执行的话,这里的++$num
就会在perl环境下执行自增操做,$1
也会被替换成已匹配的分组,最后完成s的替换。
下面的示例可能会更容易理解一些:
$ perl -pe 's/(\w+)/++$num/ge' file.log 1 2: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
若是想让它们顶格输出,能够继续删除行首空白:
$ perl -pe 's/(\w+)/++$num/ge;s/^\s+//' file.log 1 2: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
再好比,每行的单词单独计数,只需在每次读入行的开头进行计数器重置便可:
$ perl -pe '$num=0;s/(\w+)/"<".++$num.">.$1"/ge' file.log <1>.first <2>.paragraph: <1>.first <2>.line <3>.in <4>.1st <5>.paragraph <1>.second <2>.line <3>.in <4>.1st <5>.paragraph <1>.third <2>.line <3>.in <4>.1st <5>.paragraph