1. 程式人生 > >Perl一行式:處理空白符號

Perl一行式:處理空白符號

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


假如檔案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

每行後加一空行

$ perl -pe '$\ = "\n"' 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

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

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

perl的"-p"選項表示print操作,即對每一讀入的行在經過表示式操作後都預設輸出,和sed的p命令是一樣的。

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

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

Perl中的continue和其它語言的continue有點不一樣,Perl中的continue表示每輪迴圈的主體執行完之後,執行另一段程式碼。也就是說,每一行內容經過"-e"指定的表示式處理後,都會被continue程式碼塊中的print輸出。

解釋下-p選項的過程:while(<>)每次讀取一行資料賦值給預設變數$_,然後經過-e的表示式進行處理,處理完後執行continue的print,這裡print沒有引數,所以表示輸出預設變數$_的內容,也就是被處理後的行資料。

另一個選項 -n ,表示處理檔案但預設不輸出處理後的行。如果想要輸出,只能自己在-e表示式中指定輸出操作(print/say/printf)。邏輯為:

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

也就是說,-n和-p兩個選項會自動讀取檔案(如果都存在,則-p會覆蓋-n),不需要在-e的表示式中自己寫讀取檔案的邏輯。如果沒有這兩個選項,那麼在-e中要自己寫才能讀引數檔案:

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

最後是關於特殊變數$\表示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

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