一個同學問我一個問題,說有以下文件內容,要求輸出爲特定的格式。這裏就獻醜給出一個處理的方法吧,由於時間關係可能我的答案並不是最好的,但是我儘量將我的答案講解明白,讓你理解處理的方法。如果您有簡單明瞭的處理方法請不嗇賜教!
題目 |
文件內容如下:
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' 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的-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 2016-12-08 00:09 血戰鋼鋸嶺 2016-12-08 03:01 你的名字 2016-12-09 10:59 我不是潘金蓮 # |
執行如下命令,結果表明\1代表分組1,自然\3和\5代表分組3和5
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1/" file1 2016-12-08 2016-12-08 2016-12-09 # |
那麼\n代表什麼呢?我們在現有命令上加上/n,結果表明\n代表換行,如下所示:
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1\n/" file1 2016-12-08
2016-12-08
2016-12-09
# |
\t不用解釋了吧,完整的執行以下如下所示:
# sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})(\ |\t)(.*)(\ |\t)(.*)/\1\n\3\t\5/" file1 2016-12-08 00:09 血戰鋼鋸嶺 2016-12-08 03:01 你的名字 2016-12-09 10:59 我不是潘金蓮 # 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 2016-12-08 00:09 血戰鋼鋸嶺 03:01 你的名字 2016-12-09 10:59 我不是潘金蓮 # |
主要有多行模式空間的操作命令N、D、P和sed腳本流程控制命令b、t。
多行模式空間(MultilinePattern Space):就是在模式空間中放置輸入文件的多個行內容,操作多行模式空間的有N、D、P含義如下:
·N命令:是將下一行也輸入到模式空間中,當前行與下一行之間插入一個’\n’,以下爲示意圖
·D命令:僅刪除Multiline Space中第一個’\n’之前的內容,如上圖,即刪除“The UnixOperating System”,而“Is A interestingSystem”仍然存在。同時,它使得腳本的控制流轉到腳本文件的第一行,跳過該命令的後續命令。
·P命令:僅打印Multiline Space中第一個’\n’之前的內容,如上圖,即僅打印“The UnixOperating System”。
我們看下N的範例,還是使用file1文件中的內容,命令執行結果如下:
# sed ‘N’ file1 2016-12-08 00:09 血戰鋼鋸嶺 2016-12-08 03:01 你的名字 2016-12-09 10:59 我不是潘金蓮 # |
看到結果後,有同學會說,這和Sed不加任何的選項和命令,執行的結果相同。如下所示:
# sed '' file1 2016-12-08 00:09 血戰鋼鋸嶺 2016-12-08 03:01 你的名字 2016-12-09 10:59 我不是潘金蓮 # |
使用肉眼咋一看真發現不了區別,首先我們回顧下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 你的名字 2016-12-09 10:59 我不是潘金蓮 # |
通常,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命令。
到此,你能看明白後半句的意思嗎?歡迎留言!
歡迎使用微信關注“雲運維聯盟”公衆號,第一時間瞭解本博客動態!