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