Learning Objectives
- 用 grep -E(egrep)搭配延伸正規表達式的 +、?、|、() 搜尋文字
- 用 sed 的 s/// 取代文字,用 g 旗標取代一行中所有符合的地方,用 Nd 刪除行、用 -n 搭配 p 只印出指定的行
- 知道 sed -i 會直接修改檔案,修改前先不加 -i 看一次結果
- 用 awk 印出指定的欄位,用 -F 指定分隔字元,用條件只處理符合的行
從「找到」到「改掉」與「挑出欄位」
上一課用 grep 與基本正規表達式(BRE)找出符合條件的行。這一課往前走三步:
| 工具 | 做什麼 | 例子 |
|---|---|---|
grep -E(egrep) |
用延伸正規表達式(ERE)搜尋,可以用的特殊符號比上一課更多 | 找出 god、good、goood |
sed |
串流編輯器(stream editor):逐行讀入、取代或刪除,再輸出 | 把 cpu 全部改成 memory |
awk |
把每一行切成欄位,挑出需要的欄位,或只處理符合條件的行 | 從 /etc/passwd 列出帳號與 UID |
awk 的名稱來自三位作者 Alfred Aho、Peter Weinberger、Brian Kernighan 的姓氏字首,它本身是一個直譯式的小語言,很適合處理一行一筆的文字資料。
這三個工具預設都不會修改原本的檔案:結果印在螢幕上,要存下來就用上一課的重導
>。只有sed -i會直接改檔案(本課後半說明)。
延伸正規表達式的四個符號
以下用一個練習檔 ere.txt 說明,內容是:
(gd)
(god)
(good)
(goood)
(glad)
+:前一個字元出現一次以上。egrep -n 'go+d' ere.txt找到2:(god)、3:(good)、4:(goood)?:前一個字元出現零次或一次。egrep -n 'go?d' ere.txt找到1:(gd)、2:(god)|:或,符合其中一個即可。egrep -n 'gd|good' ere.txt找到1:(gd)、3:(good)():群組,把幾個字元當成一個單位。egrep -n 'g(la|oo)d' ere.txt找到3:(good)、5:(glad)
注意 go+d 不會找到 (gd):+ 要求 o 至少出現一次;go?d 也不會找到 (good):? 最多只允許一個 o。
egrep 與 grep -E
egrep 就是 grep -E。GNU grep 從 2.5.3 版(2007 年)起就把 egrep 列為不建議使用,3.8 版起執行 egrep 會先印出警告:
egrep: warning: egrep is obsolescent; using grep -E
Debian 與 Ubuntu 的 grep 套件把這個警告拿掉了,所以在 Ubuntu 24.04 執行 egrep 不會看到它,但寫腳本時請改用 grep -E,換到別的發行版本也能正常執行。
不加 -E 時這些符號是什麼?
在基本正規表達式(BRE,grep 不加 -E)裡,+、?、|、() 都是普通字元。grep -n 'go+d' ere.txt 找的是「g、o、加號、d」這四個字,所以什麼都找不到。GNU grep 的 BRE 要在前面加反斜線 \+、\?、\|、\(\) 才有特殊意義。與其記兩套寫法,需要這些符號時直接加 -E 比較清楚。
sed:取代、刪除、只印出指定的行
sed 一次讀一行,依照你給的指令處理後輸出。最常用的寫法:
sed [選項] '[位址]指令' 檔案
| 寫法 | 作用 |
|---|---|
sed 's/cpu/memory/' test.txt |
每一行第一個 cpu 改成 memory(區分大小寫) |
sed 's/cpu/memory/g' test.txt |
g 旗標:一行裡所有的 cpu 都改 |
sed '1,8 s/[Tt]he/THE/g' MyFile.txt |
只處理第 1~8 行,[Tt] 讓 the 與 The 都符合 |
sed '1d' test.txt |
刪除第 1 行;'2,3d' 刪除第 2~3 行,'/^#/d' 刪除以 # 開頭的行 |
sed -n '2p' test.txt |
-n 關掉「自動印出每一行」,只印出 p 指定的行 |
sed -n '/cpu/p' test.txt |
只印出含有 cpu 的行,效果類似 grep cpu |
sed -e 's/a/b/' -e '3d' 檔案 |
-e 可以一次給好幾個指令 |
加上 -E(舊寫法 -r)就能在 sed 裡使用延伸正規表達式,例如用括號群組與 \1(第一組括號比對到的文字)把 cpu 或 memory 用中括號標出來:
sed -E 's/(cpu|memory)/[\1]/g' test.txt
用投影片的對照來記:s 是命令、cpu 是要找的樣板、memory 是取代成的文字、g 是旗幟、1,8 是位址。
sed -i 會直接改檔案
sed -i 's/cpu/CPU/g' test.txt
加上 -i(in-place)後,sed 不再把結果印到螢幕,而是直接寫回檔案,沒有確認、也沒有復原。建議的習慣:
- 先不加
-i執行一次,確認螢幕上的結果正確 - 再加上
-i;重要的檔案先複製一份,或寫成sed -i.bak ...,GNU sed 會把原檔另存成test.txt.bak
系統設定檔(例如 /etc 底下)屬於 root,修改時要 sudo sed -i ...,下手前更要先看過結果。
awk:挑出欄位與條件篩選
awk 預設用空白(空格或 Tab)把每一行切成欄位:$1 是第一欄、$2 是第二欄,$0 是整行,NF 是這一行的欄位數,NR 是行號。
ls -l /usr | awk '{print $4,$9}'
ls -l 的第 4 欄是群組、第 9 欄是名稱,所以會印出 root bin、root lib⋯⋯。print 裡的逗號會輸出成一個空格。
第一行 total 16 只有兩欄,第 4、9 欄是空的,所以印出一個空白行。
| 寫法 | 作用 |
|---|---|
awk -F: '{print $1}' /etc/passwd |
-F: 用冒號分欄,印出每個帳號名稱(投影片寫成 -F":",效果相同) |
awk -F: '{print $1,$3}' /etc/passwd |
印出帳號與 UID,例如 root 0、student 1000 |
awk -F: '$3>=1000 {print $1}' /etc/passwd |
只處理第 3 欄大於等於 1000 的行:Ubuntu 一般使用者的 UID 從 1000 開始 |
awk '/cpu/ {print NR, $0}' test.txt |
只處理含有 cpu 的行,印出行號與整行 |
條件寫在大括號前面,動作寫在大括號裡面;整段程式要用單引號包起來,避免
$1被 shell 當成變數展開。 在真的 Ubuntu 上,$3>=1000還會列出nobody:它的 UID 是 65534,是系統保留的帳號,不是真的使用者。
想一想:延伸正規表達式
ere.txt 有 (gd)、(god)、(good)、(goood)、(glad) 五行,grep -E 'go+d' ere.txt 會找到幾行?
想同時找出含有 error 或 warning 的行,下列哪一個寫法正確?
在 Ubuntu 24.04 執行 egrep 時,下列說法何者正確?
想一想:sed 與 awk
某一行是 cat cat cat,執行 sed 's/cat/dog/' 後這一行變成什麼?
執行 sed 's/8080/9090/g' config.txt 之後,config.txt 的內容會怎樣?
/etc/passwd 的一行是 student:x:1000:1000:Student,,,:/home/student:/bin/bash,awk -F: '{print $1,$6}' 會印出什麼?
重點精華回顧
- 延伸正規表達式(
grep -E):+一次以上、?零次或一次、|或、()群組;不加-E時這些符號是普通字元。 egrep等於grep -E,已被列為淘汰的寫法;腳本裡請寫grep -E。sed 's/舊/新/'取代每一行第一個符合處,加g取代全部;Nd刪除第 N 行,-n搭配p只印出指定的行,-E使用延伸正規表達式。sed預設不改檔案;sed -i直接寫回檔案,先不加-i確認結果,必要時用-i.bak留備份。awk '{print $1,$3}'印出指定欄位,-F:指定分隔字元,'$3>=1000 {...}'只處理符合條件的行。