Learning Objectives
- 用 tr 轉換大小寫、刪除字元(-d、-d -c),以及把連續重複的字元壓縮成一個(-s)
- 用 cut 依分隔符號取出欄位(-d、-f),或依位置取出字元(-c)
- 用 sort 排序,並用 -n、-r、-t、-k、-u 控制排序方式
- 用 uniq 去除相鄰的重複行、用 -c 計算次數,並把這些指令串成管線統計出現次數排行
一個指令只做一件事
上一課學會了管線 |。這一課的四個指令都是「從標準輸入讀進一行一行的文字,處理後送到標準輸出」,
所以特別適合放在管線中間,像工廠的生產線一樣一站接一站處理資料:
| 指令 | 做的事 | 英文由來 |
|---|---|---|
tr |
把字元換成別的字元,或刪掉某些字元 | translate |
cut |
從每一行切出某幾個欄位或某幾個字元 | cut |
sort |
把所有行排序 | sort |
uniq |
把相鄰的重複行合併成一行 | unique |
每個指令都很單純,組合起來卻能完成「統計紀錄檔裡哪一種事件最多」這類工作。 Unix 的設計傳統就是讓每個程式只做好一件事,再用管線組合。
tr:轉換與刪除字元
tr [選項] 字元集1 [字元集2]
tr 只從標準輸入讀資料,後面不能直接接檔名;要處理檔案時寫成 tr … < 檔案 或 cat 檔案 | tr …。
| 寫法 | 作用 |
|---|---|
tr 'A-Z' 'a-z' |
字元集1 的每個字元換成字元集2 對應位置的字元:大寫轉小寫 |
tr '[:upper:]' '[:lower:]' |
同上,用字元類別表示;還有 [:digit:](數字)、[:space:](空白字元) |
tr -d '0-9' |
刪除(delete)字元集1 裡的字元 |
tr -d -c '0-9 \n' |
-c 取補集(complement):刪除「不是數字、空白、換行」的字元,等於只留下這些 |
tr -s ' ' |
壓縮(squeeze):連續出現的同一個字元只留一個 |
tr -s '\n' ',' |
先把換行換成逗號,再把連續的逗號壓縮成一個 |
投影片的例子:
$ echo "HELLO WORLD 123" | tr 'A-Z' 'a-z'
hello world 123
$ echo "HELLO WORLD 123" | tr -d '0-9'
HELLO WORLD
$ echo "@HELLO WORLD 123" | tr -d -c '0-9 \n'
123
最後一個例子留下了兩個空白(原本在 HELLO 與 WORLD 後面)和 123。
\n 代表換行、\t 代表 Tab、\r 代表歸位字元。Windows 的文字檔每一行結尾是 \r\n 兩個字元,
投影片的 tr -s "\r\n" "," 就是用來處理從 Windows 複製過來的檔案;Linux 的文字檔只有 \n。
cut:切出欄位或字元
cut -d 分隔符號 -f 欄位 [檔案] # 依欄位切
cut -c 字元位置 [檔案] # 依字元位置切
-d(delimiter)指定欄位之間的分隔符號,不指定時預設是 Tab;-f(fields)指定要哪幾個欄位,從 1 開始編號:
| 寫法 | 取出 |
|---|---|
-f 2 |
第 2 欄 |
-f 1,5 |
第 1 欄與第 5 欄 |
-f 2-4 |
第 2 欄到第 4 欄 |
-f 3- |
第 3 欄到最後 |
-f -3 |
第 1 欄到第 3 欄 |
-c(characters)的寫法相同,只是數的是字元位置,例如 -c 2-10 取每一行的第 2~10 個字元。
投影片的例子:
$ cut -d , -f 2 data.csv
3.2
3
3.2
3.1
7
$ ls -l /etc | tail -n 5 | cut -c 2-10
rwxr-xr-x
rw-r--r--
…
ls -l 每一行的第 1 個字元是檔案類型,第 2~10 個字元正好是權限。
/etc/passwd 每一行用冒號 : 分成 7 欄(帳號、密碼佔位、UID、GID、說明、家目錄、登入 shell),
cut -d : -f 1,7 /etc/passwd 就能列出每個帳號用哪一個 shell。
欄位之間用「好幾個空白」對齊的輸出(例如
ls -l),用cut -d ' '切會遇到空欄位,比較不好用; 這種情況改用-c依位置切,或第 15 課的awk。
sort 與 uniq:排序與去除重複
sort 預設把每一行當成字串,依字典順序由小到大排列:
| 選項 | 作用 |
|---|---|
-n |
依數值大小排序(numeric);不加時 10 會排在 9 前面,因為是一個字一個字比 |
-r |
反過來排(reverse),由大到小 |
-t 符號 |
指定欄位的分隔符號(預設是空白) |
-k 2,2 |
只用第 2 欄當排序依據;寫 -k 2 是從第 2 欄一直到行尾 |
-u |
排序後重複的行只留一行(unique) |
-f |
不分大小寫 |
投影片的 os.txt 依第 2 欄的數字排序:
$ sort -t , -k 2,2 -n os.txt
UbuntuOS,919
Window,1303
Apple,1677
Mac,2830
uniq 把相鄰的重複行合併成一行。它只會和上一行比較,所以重複的行如果分散在檔案各處,就去不乾淨;
先用 sort 讓相同的行排在一起,再交給 uniq,是最常見的用法。
| 寫法 | 作用 |
|---|---|
uniq 檔案 |
相鄰的重複行只留一行 |
uniq -c |
每一行前面加上它連續出現的次數(count) |
uniq -d |
只列出有重複的行 |
uniq -u |
只列出沒有重複的行 |
sort 檔案 | uniq 和 sort -u 檔案 的結果相同;要計算次數時就需要 uniq -c。
組合:統計出現次數排行
/var/log/dpkg.log 每一行的第 3 欄是 dpkg 做的動作(install、status、remove…)。想知道哪一種動作最多:
cut -d ' ' -f 3 /var/log/dpkg.log | sort | uniq -c | sort -nr | head -n 3
cut取出第 3 欄(以一個空白為分隔)sort讓相同的動作排在一起uniq -c計算每一種出現幾次sort -nr依次數由大到小排head -n 3只留前三名
把第 3 欄換成其他欄位或其他紀錄檔,就能統計「哪個 IP 連線最多」「哪個帳號登入最多次」這類問題。
想一想:tr 與 cut
echo "Hello 2026" | tr -d 'a-z' 會印出什麼?
/etc/passwd 以冒號分隔欄位,第 1 欄是帳號、第 6 欄是家目錄。哪一個指令能列出這兩欄?
echo "a b c" | tr -s ' ' 會印出什麼?
想一想:sort 與 uniq
為什麼通常要先 sort 再 uniq?
nums.txt 有三行:10、9、100。執行 sort nums.txt(不加 -n)的結果依序是?
access.log 每一行的第 1 欄是連線的 IP(以空白分隔)。要列出連線次數由多到少的 IP,哪一條管線正確?
重點精華回顧
tr只讀標準輸入:tr 'A-Z' 'a-z'轉換、-d刪除、-d -c只留下指定的字元、-s壓縮連續重複的字元。cut -d 分隔符號 -f 欄位切欄位(預設分隔符號是 Tab),cut -c 位置切字元;欄位寫法有2、1,5、2-4、3-。sort預設依字典順序;-n數值、-r反向、-t分隔符號、-k 2,2指定欄位、-u去除重複。uniq只合併相鄰的重複行,所以先sort;-c計算次數、-d只列重複的行。cut … | sort | uniq -c | sort -nr | head是統計出現次數排行的固定套路。