Perl一行式:處理空白符號

perl一行式程序系列文章Perl一行式html


假如文件file.log內容以下:express

root   x 0     0 root   /root     /bin/bash
daemon x 1     1 daemon /usr/sbin /usr/sbin/nologin
bin    x 2     2 bin    /bin      /usr/sbin/nologin
sys    x 3     3 sys    /dev      /usr/sbin/nologin
sync   x 4 65534 sync   /bin      /bin/sync

每行後加一空行

$ perl -pe '$\ = "\n"' file.log

結果:bash

root   x 0     0 root   /root     /bin/bash

daemon x 1     1 daemon /usr/sbin /usr/sbin/nologin

bin    x 2     2 bin    /bin      /usr/sbin/nologin

sys    x 3     3 sys    /dev      /usr/sbin/nologin

sync   x 4 65534 sync   /bin      /bin/sync

這裏出現了選項 -p 和 -e,出現了特殊變量$\,附帶的,稍後還會解釋另外一個選項 -n。less

perl的"-e"選項表示後面接perl的一行式表達式,就像sed的-e選項同樣。這是一行式perl程序最多見的一個選項。perl還有一個"-E"選項,和"-e"同樣都用來指定一行式表達式,但"-E"可使用一些高版本的功能。函數

perl的"-p"選項表示print操做,即對每一讀入的行在通過表達式操做後都默認輸出,和sed的p命令是同樣的。測試

實際上,perl中的"-p"選項等價於下面的邏輯,理解了這個邏輯,對理解sed的邏輯會頗有幫助。code

while(<>){
    ... -e 指定的表達式代碼在這裏 ...
} continue {
    print or die "-p failed: $!\n";
}

Perl中的continue和其它語言的continue有點不同,Perl中的continue表示每輪循環的主體執行完以後,執行另外一段代碼。也就是說,每一行內容通過"-e"指定的表達式處理後,都會被continue代碼塊中的print輸出。htm

解釋下-p選項的過程:while(<>)每次讀取一行數據賦值給默認變量$_,而後通過-e的表達式進行處理,處理完後執行continue的print,這裏print沒有參數,因此表示輸出默認變量$_的內容,也就是被處理後的行數據。對象

另外一個選項 -n ,表示處理文件但默認不輸出處理後的行。若是想要輸出,只能本身在-e表達式中指定輸出操做(print/say/printf)。邏輯爲:blog

while (<>) {
    ...  # -e expression here
}

也就是說,-n和-p兩個選項會自動讀取文件(若是都存在,則-p會覆蓋-n),不須要在-e的表達式中本身寫讀取文件的邏輯。若是沒有這兩個選項,那麼在-e中要本身寫才能讀參數文件:

$ perl -e 'while(<>){...}'

通常來講,選擇使用-p仍是-n的規則以下:

  • 某些行不須要輸出或者須要被刪除的時候,不該該使用-p,由於它默認會輸出全部行
    • 換句話說,若是不須要輸出全部行時,不使用-p,須要輸出全部行,能夠考慮使用-p
  • 不使用-p的時候,幾乎均可以使用-n

最後是關於特殊變量$\表示print的輸出行分隔符(awk的ORS變量),它默認爲undef,因此print輸出的每段數據之間都是緊連在一塊兒的。此處示例將$\指定爲換行符。

因爲<>讀取數據時已經將文本中每一行的\n也讀取了,因此加上$\已經有兩個連續的\n,因而每行後面都會多一空行。

實際上沒有必要爲每一讀入的行都設置$\,能夠將它設置在BEGIN塊中:

$ perl -pe 'BEGIN{$\ = "\n"}' file.log

對於本示例"每行以後加上空行"有多種解決方式。例如:

$ perl -pe '$_ .= "\n"' file.log
$ perl -nE 'say "$_\n"' file.log
$ perl -pe 's/$/\n/' file.log
......

每行後加空行,但空行除外

$ perl -pe '$_ .= "\n" unless /^$/'

這裏使用unless邏輯進行空行匹配,若是匹配空行,就不對當前行追加尾隨換行符。unless測試等價於if的否認測試。

有些空行多是包含了空白符號(空格、製表符等)的行,這些空白肉眼不可識別,但卻佔了字符空間,使得沒法匹配^$,因此更好的匹配模式是:

$ perl -pe '$_ .= "\n" if /\S/'

\S表示任意單個非空白字符,\s表示任意單個空白字符。因此這裏的邏輯是:只要行能匹配非空白字符,就追加尾隨換行符。

每行後加N空行

想在每行後面加兩空行、三空行、四空行、N空行該如何解決?

$ perl -pe '$_ .= "\n" x 3' file.log

Perl的字符串可使用小寫字母x表示重複N次,例如"a" x 3獲得"aaa","ab" x 2獲得"abab"。上面的示例表示爲每行都追加3個換行符。

經過字符串重複操做,能夠很輕鬆地輸出等長的分割線:

$ perl -e '
        print "-" x 30,"\n";
        print "hahaha\n";
        print "-" x 30,"\n";
        print "heihei\n";'
------------------------------
hahaha
------------------------------
heihei

每行前加空行

最簡單的方式是使用s替換操做。

$ perl -pe 's/^/\n/' file.log

移除全部空白行

$ perl -ne 'print unless /^$/' file.log

此處使用了"-n"選項,表示禁止默認的輸出。print和匹配操做的對象都使用默認變量$_。等價於:

整個邏輯是:只要匹配了空行/^$/,就不輸出。

這也有好幾種方式能夠實現,例如:

$ perl -ne 'print if /\S/' file.log

比較獨特的一種實現方式是使用length函數:

$ perl -lne 'print if length' file.log

這裏涉及選項"-l"和函數length(),且print和length都沒有指定操做對象,因此使用默認變量$_,等價於:

$ perl -lne 'print $_ if length $_' file.log

length函數能夠獲取字符串的字符個數,注意是字符數不是字節數。

-l選項在結合-n或-p使用的時,會自動對讀入的行移除尾隨換行符,而後在輸出的時候自動追加尾隨輸出分隔符(如換行符,如何追加分隔符請參看Perl一行式參考手冊)。

這裏的邏輯是:若是是空行,那麼在被-l移除換行符後length返回0,也就是布爾假,因此只有不是空行的行纔會被輸出。

壓縮連續空行:按段落讀取

先準備一段測試數據paragraph.log:

first paragraph:
        first line in 1st paragraph
        second line in 1st paragraph
        third line in 1st paragraph


second paragraph:
        first line in 2nd paragraph
        second line in 2nd paragraph
        third line in 2nd paragraph


third paragraph:
        first line in 3rd paragraph
        second line in 3rd paragraph
        third line in 3rd paragraph

sed/awk中想要壓縮連續空行,總要多讀入幾行進行連續空行的判斷。例如:

$ sed -nr '$!N;/^\n$/!P;D' paragraph.log

但在perl一行式中,這會變得無比的簡單:

$ perl -00pe '' paragraph.log

這裏兩個關注點:-00-e ''

-e ''的表達式部分爲空,表示什麼也不作。什麼也不作的時候,也能夠寫成-e0

$ perl -00pe0 paragraph.log

-0OCTNUM表示設置輸入行分隔符$/

若是省略8進制值OCTNUM,則-0表示設置$/爲undef,即$/ = undef,也就是一次性從文件頭讀到文件尾看成一行賦值給$_

這裏指定了8進制的值爲0,對應於ASCII的空字符串,即等價於$/ = "",它表示按段落讀取(slurp讀取模式),並壓縮連續的空行爲單個空行。

什麼是段落?中間隔了至少一空行的是上下兩個段落,段落意味着可能包含了連續的多行。可是若是隔了連續的空行呢?設置$/ = ""會按段落讀取,並壓縮連續的空行爲單空行,而後做爲上面的段落的一部分。設置$/ = "\n\n"也表示按段落讀取,但它不會壓縮連續的空行。

如何知道是不是按段落讀取?可用下面的示例進行測試:

$ perl -ne '
    BEGIN{$/ = "";}
    print $_."xxxxx" if /2nd/' paragraph.log

會發現追加的幾個字符"xxxxx"是單獨附加在第二段落的尾部的,而不是能匹配"2nd"的每一行上。

壓縮/擴展全部連續空行爲N空行

在上面一節壓縮連續空行的基礎上,實現這個目的已經很是容易了:

$ perl -00pe '$_ .= "\n" x 3' paragraph.log

這表示將每一個段落之間規範爲4個連續的空行進行分隔。之因此是4空行而不是3,是由於壓縮成單空行後,又追加了3空行。

壓縮/擴展單詞間的空格數量

要實現這樣的功能,這個對於sed來講也很是的容易。這裏給幾個簡單示例。

1.每行單詞間的空給雙倍化:每一個空白都擴成2空格

$ perl -lpe 's/ /  /g' file.log

2.移除每行單詞間的全部空白

$ perl -lpe 's/ //g' file.log

3.每行單詞間連續空白壓縮爲單空格

$ perl -lpe 's/\s+/ /g' file.log

4.全部字符間插入一個空格

$ perl -lpe 's// /g' file.log

注意,上面插入空格時,也會在行首和行尾插入空格符號。

直接修改文件

perl的"-i"選項能夠用來原地修改、拷貝副本。用法和sed的"-i"一致。

例如:

$ perl -i".bak" -lpe 's/$/\n/g' file.log

$ cat file.log
root   x 0     0 root   /root     /bin/bash

daemon x 1     1 daemon /usr/sbin /usr/sbin/nologin

bin    x 2     2 bin    /bin      /usr/sbin/nologin

sys    x 3     3 sys    /dev      /usr/sbin/nologin

sync   x 4 65534 sync   /bin      /bin/sync

$ cat file.log.bak
root   x 0     0 root   /root     /bin/bash
daemon x 1     1 daemon /usr/sbin /usr/sbin/nologin
bin    x 2     2 bin    /bin      /usr/sbin/nologin
sys    x 3     3 sys    /dev      /usr/sbin/nologin
sync   x 4 65534 sync   /bin      /bin/sync