cut命令_Linux cut命令:剪下檔案中的資料

2020-07-16 10:04:30
正如其名,cut 小能手的專長就是“剪”。具體來說,就是在檔案中負責剪下資料用的。cut 是以每一行為一個處理物件的,這種機制和 sed 命令一樣。

為了讓大家對 cut 有一個初步印象,我們來舉一個例子。當你執行 who 命令時,會輸出類似下面的內容:
[[email protected] ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)

如果我們想提取每一行的第 3 個位元組,就這樣:
[[email protected] ~]$ who|cut -b 3
c
c
c

看明白了吧,-b選項後面可以設定要提取哪一個位元組,其實 -b 和 3 之間沒有空格也是可以的,但推薦有空格,提高可讀性。

cut 的定位依據

所謂“定位依據”,通俗地講就是,我該如何告訴 cut 我想定位到哪一段內容進行剪下呢?

其實,cut 命令共接受三類定位方法:
  • 第一,按位元組(bytes)定位,用-b選項。
  • 第二,按字元(characters)定位,用-c選項。
  • 第三,按域(fields)定位,用-f選項。

好像不太懂呢,沒關係,在下面的內容中,我們會為大家詳細介紹這幾種定位方法的知識和技巧,現在就開始!

位元組定位的技巧

在“位元組”定位中,如果我想提取每一行的第 3、第 4、第 5 和第 8 個位元組,該怎麼辦呢?

其實,-b選項支援形如“3-5”這樣的寫法,而且多個定位數位之間還可以用逗號隔開,還是讓我們看看例子吧:
#在本篇文章中, 為了範例需要, 都使用了rocrocket這個賬戶名
[[email protected] ~]$ who|cut -b 3-5,8
croe
croe
croe

但有一點要注意,cut 命令如果使用了-b選項,那麼在執行此命令時,cut 會先把 -b 後面所有的定位數位按照從小到大的順序排序,然後再依次提取。所以你可以隨意顛倒定位數位的順序。比如下面這個例子就可以證明這個結論:
[[email protected] ~]$ who|cut -b 8,3-5
croe
croe
croe
看,即使我們把定位數位的順序顛倒著寫,所提取出的內容仍然是 croe,沒有一絲絲的變動。

有關定位數位的小技巧

定位數位的設定,其實是非常靈活的,比如我們只限定最大定位數或最小定位數,都是可以的,來,看例子:
#who命令的輸出內容, 我們繼續以它為例子啦
[[email protected] ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)
 
#-3就表示從頭到第3個位元組
[[email protected] ~]$ who|cut -b -3
roc
roc
roc
 
#3-就表示從第3個位元組到結尾
[[email protected] ~]$ who|cut -b 3-
crocket :0           2016-03-29 11:07
crocket pts/0        2016-03-29 11:23 (:0.0)
crocket pts/1        2016-03-29 14:15 (:0.0)

想必你也看到了,-3 表示從第一個位元組到第三個位元組,而 3- 表示從第三個位元組到行尾。如果你細心,可以看到這兩種情況下,都包括了第三個位元組“c”,說明 cut 對於區間採用的都是“閉區間”。

如果你是一位 GEEK,那麼我們再為你介紹一個知識細節。如果我們執行 who|cut-b-3,3-,你覺得會如何呢?是否會出現兩個 c 字元呢?
[[email protected] ~]$ who|cut -b -3,3-
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)
答案是並沒有出現連續兩個重疊的 c 字元。

來說說字元定位吧

首先我們給大家舉一個以字元為定位標誌的最簡單的例子。

下面的例子你應該似曾相識吧,提取第 3、第 4、第 5 和第 8 個字元:
[[email protected] ~]$ who|cut -c 3-5,8
croe
croe
croe

不過,看著怎麼和 -b 沒有什麼區別啊?莫非 -b 和 -c 作用一樣?其實不然,看似相同,只是因為這個例子舉得不好,who 輸出的都是單位元組字元,所以用 -b 和 -c 沒有區別,如果我們提取中文,區別就看出來了。來,看看中文提取的情況:
#這是我們的檔案內容, 就以它來舉例
[[email protected] ~]$ cat cut_ch.txt
星期一
星期二
星期三
星期四
 
#我們仍然採用位元組定位來試試, 哇, 都是亂碼
[[email protected] ~]$ cut -b 3 cut_ch.txt
?
?
?
?
 
#我們改用字元定位, 看, 效果還不錯, 亂碼消失了
[[email protected] ~]$ cut -c 3 cut_ch.txt
一
二
三
四
看到了吧,用 -c 則會以字元為單位來提取內容;而 -b 只會傻傻地以位元組(8 位二進位制位)來計算,輸出的就是亂碼了。

既然提到了這個知識點,就再補充一句,也是為 GEEK 準備的哦。當遇到多位元組字元時,可以使用-n選項,-n選項用於告訴 cut 命令不要將多位元組字元拆開,而是合併在一起顯示。例子如下:
#看好, 用-b是亂碼
[[email protected] ~]$ cat cut_ch.txt |cut -b 2
?
?
?
?
 
#而我們知道三個位元組組成一個漢字, 於是用了-n選項, 這回就可以正確顯示了
[[email protected] ~]$ cat cut_ch.txt |cut -nb 1,2,3
星
星
星
星

按域定位好奇怪

還記得我們在本文開頭說的“cut 的三種定位方法”麼,其中的第三個方法是“按域定位法”。那為什麼會有“域”呢,是不是很奇怪?下面來為大家解釋一下。

因為剛才提到的 -b 和 -c 只能在固定格式的文件中提取資訊,而對於非固定格式的文件則束手無策。這時候“域”就派上用場啦。

下面的講解內容是在假設你對 /etc/passwd 檔案的內容和組織形式比較了解的情況下進行的,所以,如果你不了解 /etc/passwd 檔案結構,建議一定要先補習一下相關的知識。

如果你觀察過 /etc/passwd 檔案,你會發現,它並不像 who 的輸出資訊那樣具有固定的格式,而是比較零散的樣子。但是,所幸的是,檔案中有不少冒號,這些冒號在這個檔案的每一行中都起到了非常重要的作用,它恰當地隔開了每一個有含義的項。這裡“有含義的項”,我們就可以認為是“域”。

很幸運,cut 命令支援“按照冒號為間隔”進行內容提取,具體地說就是可以“設定間隔符”,並指定“提取第幾個域”。

以 /etc/passwd 的前五行內容為例:
#展示/etc/passwd檔案的前五行
[[email protected] ~]$ cat /etc/passwd|head -n 5
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
 
#域派上用場啦, 厲害吧
[[email protected] ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1
root
bin
daemon
adm
lp
看到了吧,用 -d 來設定間隔符為冒號,然後用 -f 來設定我要提取的第一個域,再按回車,所有的使用者名稱就都列出來了,有成就感吧。

當然,在設定 -f 選項時,也可以使用如“3-5”或者“4-”類似的格式:
#域也支援設定定位數位
[[email protected] ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1,3-5
root:0:0:root
bin:1:1:bin
daemon:2:2:daemon
adm:3:4:adm
lp:4:7:lp
 
#再舉一個更複雜的例子
[[email protected] ~]$ cat /etc/passwd|head -n 5|cut -d : -f 1,3-5,7
root:0:0:root:/bin/bash
bin:1:1:bin:/sbin/nologin
daemon:2:2:daemon:/sbin/nologin
adm:3:4:adm:/sbin/nologin
lp:4:7:lp:/sbin/nologin
 
#-2的作用應該還記得吧, 就是從開頭到第2個域
[[email protected] ~]$ cat /etc/passwd|head -n 5|cut -d : -f -2
root:x
bin:x
daemon:x
adm:x
lp:x

火眼金睛識別空格和製表符

有時候,檔案內容中如果出現了製表符(Tab),是很難辨認的,現在我們就來介紹一個方法,讓大家可以看出一段空格到底是由若干個空格組成的還是由一個製表符組成的,獨門秘籍哦!
#這個檔案內容中有一段較長的空白, 在space和finish之間, 是Tab還是空格呢?
[[email protected] ~]$ cat tab_space.txt
this is tab finish.
this is several space      finish.
 
#求助sed命令
[rocrocket[email protected] ~]$ sed -n l tab_space.txt
this is tabtfinish.$
this is several spacetfinish.$

看到了吧,如果是製表符(Tab),那麼會顯示為t符號;如果是空格,就會原樣顯示。通過此方法我們就可以判斷出是製表符還是空格了,這樣也就可以更精準地去 cut 了。

注意,上面 sed-n 後面的字元是 L 的小寫字母哦,不要看錯。字母“l”、數位“1”、豎線“|”,這三個字元比製表符還難分辨。

教你把間隔符設定為空格或製表符

cut 的 -d 選項的預設間隔符就是製表符(Tab),所以當你就是要使用製表符的時候,完全可以省略 -d 選項,而直接用 -f 選項來指定域就可以了。是不是幸福來得太突然了呢。

而如果你想將間隔符設定為空格,那麼就按下面的例子來做就好了,其實就是用兩個單引號將一個空格括起來:
[[email protected] ~]$ cat tab_space.txt |cut -d ' ' -f 1
this
this
注意,兩個單引號之間真的要有一個空格哦,不能偷懶。

而且,你只能在 -d 後面設定一個空格,不能設定多個空格,因為 cut 只允許間隔符是一個字元。
#看看寫兩個空格的後果
[[email protected] ~]$ cat tab_space.txt |cut -d '  ' -f 1
cut: the delimiter must be a single character
Try 'cut --help' for more information.

正好藉著這個例子,讓你說出 cut 命令的一個不足之處,你會如何回答呢?對,其中一個不足就是不善於處理“多空格”情況。

如果檔案裡面的某些域是由若干個空格來間隔的,那麼用 cut 就有點麻煩了,因為 cut 只擅長處理“以一個字元間隔”的文字內容。

奇怪的重複現象

我們經常會將 ps 命令和 cut 命令配合在一起使用,但總是會出現重複的行,非常詭異,這是怎麼回事呢?

我們一起來看情景重現。將 ps 命令和 cut 命令配合使用,並提取輸出內容中每行的第3個字元:
#這是ps命令的輸出, 注意輸出內容中, 第一行前面有2個空格, 第二、三行前面有1個空格
[[email protected] ~]$ ps
  PID TTY          TIME CMD
2977 pts/0    00:00:00 bash
5032 pts/0    00:00:00 ps
 
#ps命令和cut命令配合在一起使用
[[email protected] ~]$ ps|cut -b3
P
9
0
0
看,最後的 0 重複了兩次!!而且,我也試過 ps ef 或 ps aux 均有此問題。

而當 cut 和其他命令配合時,都沒有此類問題,比如 cut 和 who 配合,輸出就很正常:
#這是who的輸出
[[email protected] ~]$ who
rocrocket :0           2016-03-29 11:07
rocrocket pts/0        2016-03-29 11:23 (:0.0)
rocrocket pts/1        2016-03-29 14:15 (:0.0)
 
#這是who命令和cut命令配合的輸出
[[email protected] ~]$ who|cut -b3
c
c
c
懸念就營造到這裡啦,我們現在就來揭開這一神秘現象的面紗。

這個看似怪異的令人百思不得其解的現象,其實是這樣造成的:如果你對管道原理比較熟悉的話,你會知道 ps|cut 組合命令其實會產生兩個進程,即 ps 進程和 cut 進程。而當 ps 進程執行時,也會列出 cut 命令對應的進程的資訊,並且將所有進程的資訊都通過管道輸出給 cut 進程。所以,cut 擷取後,就多出了一行,之所以會重複上一行內容,是由於我們恰巧取到了和上一行內容相同的字元而已。

如果還是不太明白的話,我們就來測試下執行 ps 和 ps|cat 的輸出,你或許就知道原因了。
#這是ps的輸出
[[email protected] ~]$ ps
  PID TTY          TIME CMD
29208 pts/2    00:00:00 bash
55211 pts/2    00:00:00 ps
 
#這是ps|cat的輸出, 可以看到最後出現了cat進程那一行
[[email protected] ~]$ ps | cat
  PID TTY          TIME CMD
29208 pts/2    00:00:00 bash
55212 pts/2    00:00:00 ps
55213 pts/2    00:00:00 cat
看到了吧,ps|cat 會導致輸出多了一行,同理,你應該就知道為什麼 ps|cut 會多一行了吧!