天天看點

解析企業Shell面試題

   一個同學問我一個問題,說有以下檔案内容,要求輸出為特定的格式。這裡就獻醜給出一個處理的方法吧,由于時間關系可能我的答案并不是最好的,但是我盡量将我的答案講解明白,讓你了解處理的方法。如果您有簡單明了的處理方法請不啬賜教!

題目

檔案内容如下:

2016-12-08       00:09        血戰鋼鋸嶺

2016-12-08       03:01        你的名字

2016-12-08       04:00        長城

2016-12-08       04:01        薩利機長

2016-12-09       07:35        神奇動物在    

2016-12-09       09:24        湄公河行動    

2016-12-09       10:59        我不是潘金蓮        

2016-12-09       12:43        海洋奇緣

2016-12-09       14:29        神奇四俠2015

2016-12-10       16:30        死侍

2016-12-10       16:31        加勒比海盜5:死

2016-12-10       16:36        三體

2016-12-10       18:04        阿凡達2

2016-12-10       19:40        日落七次

要求輸出結果為:

2016-12-08

00:09        血戰鋼鋸嶺

03:01        你的名字

04:00        長城

04:01        薩利機長

2016-12-09

07:35        神奇動物在    

09:24        湄公河行動    

10:59        我不是潘金蓮        

12:43        海洋奇緣

14:29        神奇四俠2015

2016-12-10

16:30        死侍

16:31        加勒比海盜5:死

16:36        三體

18:04        阿凡達2

19:40        日落七次

參考答案

看到題目後,發現檔案内容中的規律,源檔案主要由年月日、時分和電影名稱組成三列,而目标檔案年月日主要是去重,每個日期隻出現了一次後換行,将當日要上映電影的時分和電影名稱按行顯示出來。

因為是檔案處理,首先我想到了sed來處理,使用sed加正規表達式将檔案内容進行分組處理,然後去除重複的“年月日”,實作代碼如下:

# sed -r  "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\t)(.*)(\t)(.*)/\1\n\3\t\5/" file  |sed -r ':1;N;s/^(\S+)((\n.*)*)\n\1$/\1\2/M;$!b1'

#

答案剖析

首先,我們先看管道前的代碼,主要是将“年月日”和“時分,電影名稱”分成了兩部分。我們看到sed的-r選項的意思是支援擴充的這規則表達式,類似grep的-P。其中([0-9]{4}-[0-9]{2}-[0-9]{2})(\t)(.*)(\t)(.*)是正規表達式和分組。

正規表達式是指用來描述或者比對一系列符合某個句法規則的字元串的單個字元串。就是用某種模式去比對一類字元串的一個公式。Sed中的正則如下表所示:

^

行的開始

$

行的結尾

.

一個字元

*

比對0個或多個*前面的字元

[]

方括号中的所有字元

\

轉義

{}

重複次數

()

分組,将比對這個表達式的字元儲存到一個臨時區域(最多儲存9個),它們可以用\1到\9來引用。

/./

比對至少有一個字元

/../

比對至少有兩個字元的行

/^#/

比對用#開頭的行

/^$/

比對空行

/}$/

比對用}結尾的行(沒有空格在後面)

/} *$/

比對用}結尾的行(可以有空格在後面)

/[abc]/

比對小寫的a或b或c

/^[^abc]/

比對開頭不是小寫的a或b或c

  比對該題目的正規表達式的含義如下:

·([0-9]{4}-[0-9]{2}-[0-9]{2}):比對數字0-9重複4次,比對年;比對數字0-9重複2次比對月和日,并使用()分組

·(\t):比對制表符,如果你使用的空格分隔,直接比對空格,可以使用(\ |\t)比對空格或者制表符

·(.*):比對任意字元

接下來解釋示範/\1\n\3\t\5/的含義,為了減少篇幅,我複制一份檔案名為file1,内容如下:

# cat file1

  執行如下指令,結果表明\1代表分組1,自然\3和\5代表分組3和5

# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\  |\t)(.*)/\1/" file1

  那麼\n代表什麼呢?我們在現有指令上加上/n,結果表明\n代表換行,如下所示:

# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\  |\t)(.*)/\1\n/" file1

  \t不用解釋了吧,完整的執行以下如下所示:

# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\  |\t)(.*)/\1\n\3\t\5/" file1

# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\  |\t)(.*)/\1\n\3\t\5/" file1 >>file2

繼續看管道後的指令的含義,之後的指令資訊量有點稍微大。

# sed -r ':1;N;s/^(\S+)((\n.*)*)\n\1$/\1\2/M;$!b1' file2

10:59        我不是潘金蓮

主要有多行模式空間的操作指令N、D、P和sed腳本流程控制指令b、t。

多行模式空間(MultilinePattern Space):就是在模式空間中放置輸入檔案的多個行内容,操作多行模式空間的有N、D、P含義如下:

·N指令:是将下一行也輸入到模式空間中,目前行與下一行之間插入一個’\n’,以下為示意圖

<a href="http://s2.51cto.com/wyfs02/M02/8C/14/wKioL1hhymzTmCllAABTPuR-WPw778.png" target="_blank"></a>

·D指令:僅删除Multiline Space中第一個’\n’之前的内容,如上圖,即删除“The UnixOperating System”,而“Is A interestingSystem”仍然存在。同時,它使得腳本的控制流轉到腳本檔案的第一行,跳過該指令的後續指令。

·P指令:僅列印Multiline Space中第一個’\n’之前的内容,如上圖,即僅列印“The UnixOperating System”。

我們看下N的範例,還是使用file1檔案中的内容,指令執行結果如下:

# sed ‘N’ file1

2016-12-09       10:59        我不是潘金蓮

看到結果後,有同學會說,這和Sed不加任何的選項和指令,執行的結果相同。如下所示:

# sed '' file1

使用肉眼咋一看真發現不了差別,首先我們回顧下sed的工作過程:

sed會先讀取文本中的第一行,到模式空間,然後執行sed指令,處理完成後,将結果發送到螢幕上。sed每處理完一行就将其從模式空間中删除,接着會讀取文本中的第二行,到模式空間,然後執行sed指令,處理完成後,将結果發送到螢幕上。重複此過程,直到文本中的最後一行,sed便結束運作。

了解sed的工作原理,我們發現沒有使用N指令時候,sed依次将文本中的行讀取到模式空間中,sed沒有做任何的指令操作,他就直接顯示到螢幕上了。

當使用N指令後,sed執行過程是sed會先讀取文本中的第一行 “2016-12-08  00:09  血戰鋼鋸嶺$”到模式空間,然後執行sed指令N,模式空間中的第一行内容後追加第二行内容生成多行模式空間的第一行内容,多行模式空間變為“2016-12-08  00:09  血戰鋼鋸嶺\n2016-12-08  03:01  你的名字$”,處理完成後,将結果發送到螢幕上。sed繼續向模式空間讀取下一行内容,本例中就是第三行,然後再次追加下一行内容,生成多行模式空間中的第二行内容,以此類推。由于本例中第三行下沒有内容,這時候執行N指令後就不會生成多行模式空間的第二行内容,是以模式空間中有“2016-12-09         10:59        我不是潘金蓮”。處理完成後,将結果發送到螢幕上。

結果表明,我們使用N指令後,前兩行輸出的是多行模式空間的内容,最後一行是模式空間的内容。由于多行模式空間合并的第一行和第二行之間有\n,是以看到輸出的格式沒有變化,為了證明這個說法,我們将\n 替換成空格,如下所示:

# sed 'N;s/\n/ /' file1

2016-12-08       00:09        血戰鋼鋸嶺 2016-12-08         03:01        你的名字

通常,sed是将編輯指令從上到下依次應用到讀入的行上,N指令能夠在一定程式上改變預設的執行流程,甚至利用N指令可以形成一個強大的循環處理流程。除此之外,其實sed還提供了分支指令(b)和測試(test)兩個指令來控制流程,這兩個指令可以跳轉到指定的标簽(label)位置繼續執行指令。标簽是以冒号開頭的标記,标簽名稱可以自定義。例如:定義一個名稱為:label标簽,如下所示:

:label

command1

/pattern/b label

command2

當執行到/pattern/b top時,如果比對pattern,則跳轉到:label标簽所在的位置,繼續執行下一個指令command1。

上面的例子用到了分支指令,分支指令的跳轉是無條件的。而與之相對的是測試指令,測試指令的跳轉是有條件的,當且僅當目前行發生成功的替換時才跳轉。

為了明白測試指令的用法,我們用它來實作file1中的内容:

# sed  -e ':1;s/2016/2017/;t1;'  file1

2017-12-08       00:09        血戰鋼鋸嶺

2017-12-08       03:01        你的名字

2017-12-09       10:59        我不是潘金蓮

我們定義了一個标簽為:1,然後在最後利用測試指令跳轉到該标簽。可能,你會覺得這裡也可以使用分支指令,但是事實上分支指令會導緻死循環,因為在它裡他沒有結束的條件。

但是測試指令就不同了,這一點直到最後才展現出來。當最後一行被s/2016/2017/指令讀入之後,2016替換成2017,此時ta繼續跳轉到最開頭,因為模式空間中的2016已經全部被替換成2017,是以替換也不會發生。之前我們說過,當且僅當目前行發生成功的替換時測試指令才跳轉。是以此時跳轉不會發生,退出sed指令。

到此,你能看明白後半句的意思嗎?歡迎留言!

     本文轉自yjlsy 51CTO部落格,原文連結:http://blog.51cto.com/baidu/1886329,如需轉載請自行聯系原作者