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
注意,上面插入空格時,也會在行首和行尾插入空格符號。