2022年8月27日 星期六

Linux 指令:sed

動機:我想了解 sed 指令的原因

最早讓我想了解 sed 指令的原因,是在某次的操作過程中,需要查看目前的 $PATH,到底包含了哪些目錄,所以我下了一道指令:

echo $PATH

結果出現了這樣的答案:

面對這麼一長串的文字,我就想如果可以依照字符:來做分割並換行顯示,那我需要的資訊更容易閱讀。於是就 google 了一下:

然後就看到了這個答案:

於是就開始了我對 sed 指令的探索之旅。

sed 指令簡介

sed 全名為 Stream EDitor,是一個使用簡單緊湊的程式語言來解析和轉換文字 Unix 實用程式,是為命令列處理資料檔案而構建的早期 Unix 指令之一。

sed 和 awk 這兩大工具時常被拿來相提並論,因二者一樣強大和對正規表示法有良好的支援。也各自有自己專屬的腳本語言(script language),sed 主要功能為自動化的修改文字檔,而 awk 可想像為超輕量級的 C 直譯語言(Interpreted language)屬通用,偏向統計和需輸出重新排版的應用。

man page 是給已熟悉的人參考用的,用 man page 學 sed 和 awk 就好像用英文字典學英文會話。基本上若直接用 man page 來覺習 sed,這相當有難度,所以若能借用他人的使用心得與範例,相信一定是一個更好的方式。

sed 主要功能:檔案字串修改

grep 雖可利用功能強大的正規表示法搜尋檔案中的字串,但沒辦法對搜尋到的字串進行刪除,取代或插入等編輯動作。補足 grep 編輯功能的工具就是 sed,更甚者 sed 可程式化的特性常用來自動化的修改文字檔。

雖然 vi 也可用來搜尋/修改檔案內容,但要人工把檔案打開,改完再存檔人力要介入很深,如熟悉 sed 的操作這些都可自動化的完成。例如公司搬家,有舊公司地址的表格文件一堆,如善用 sed 就可有效率的自動把所有文件表格上的舊地址改新地址。

sed 用法有點抽象,故對基本用法和每一參數分別說明。

sed 基本用法

sed 和其他功能強大的指令一樣,通常功能愈強語法就愈複雜和抽象,sed 也是如此。如有常自動化的修改文字檔,是值得一學的好工具。

sed 基本的用法如下:

sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]

但光看其晦澀不明的語法是不太可能會使用的,故先舉個例子,一例解千文。

範例一:

我要把檔案 "MyFile.txt" 的 1~8 行中的 "The" 或 "the" 改為大寫的 "THE" 可如下:

sed -e '1,8 s/[Tt]he/THE/g' MyFile.txt

其中:
  -e:為預設選項,一般的狀況可省略,如沒加任何選項預設是以是 sed -e 選項來執行。
  s/[Tt]he/THE/g:sed 最基本的命令,代表搜尋樣板並取代。
     s:代表搜尋樣板並取代。
     [Tt]he:代表要搜尋樣板文字。
     THE:代表將用來取代搜尋到的樣板文字。
     g:指令最後的 g 代表搜尋全部範圍。

因每一欄參數都可能複雜和抽象,有時 sed 會解讀錯誤,故一般除檔案和選項以外的參數都會用單引號 ' 把其括起來。

範例二:

如欲搜尋和取代的樣板為單字(word),可在樣板和取代字串前加一空隔(因單字間有空隔),例如:有一句子為 "This is a book",我只想把單字 "is" 變大寫,但單字 "This" 也有子串 "is",處理不好輸出會變 "ThIS IS a book"。

echo 'This is a book' | sed 's/is/IS/g'

將輸出文字:

ThIS IS a book

所以正確的指令應該下為:

echo 'This is a book' | sed 's/ is/ IS/g'

sed 進階用法

Delimiter 分隔符號:

sed 每個參數之間預設分隔符號(delimiter)是用 "/" 來區別如 sed 's/OLD/NEW/g' flie,但如要搜尋樣板有 "/" 會和分隔符號混在一起如再加上跳脫字元 "\" 會變得好像火星文看不懂。

例如:
要把 Linux 路徑 "/abc/wxy" 改為 WINDOWS 路徑表示法的 "\abc\wxy",sed 的寫法為

sed 's/\//\\/g'
這一串類火星文,真的不易一眼看出什麼是什麼,所以故 sed 允許我們可以用除了空白 ' '、換行 '\n' 以外的字元(英文字母或數字或符號皆可)來當分隔符號,只要前後一致即可。

例如:用 : 當作分隔字元:

echo "This ia an apple" | sed 's:ap:Ap:g'

例如:Linux 路徑改為 WINDOWS 路徑表示法,用 # 當作分隔字元:

echo "/home/tmps07" | sed 's#/#\\#g'

ADDRESS 位址範圍:

sed 位址的表示法可為行號或合法正規表示法的樣板,可為有起始和結束二個位址的範圍或只有單一位址(如第幾行或指定的樣板)。

有些 COMMAND 一定要配合單一位址,大部分的 COMMAND 位址的表示法為範圍,有些 COOMAND 如省略位址表示是全部(如上例搜尋並取代的 COMMAND "s")。

位址範圍的用法各如下:

位址表示法
為一個起始位址加上一個結束位址,兩者間以 "," 分隔。如只有一個位址則為固定單一位址。

$ sed '1,5 s/ [aA]/ one/g'  file # 將 1~5 行 "a" 或 "A" 改 "one"
$ sed '5 s/ [aA]/ one/g' file # 只將第 5 行 "a" 或 "A" 改 "one"
$ sed 's/ [aA]/ one/g' file # 省略位址, 整個檔案 "a" 或 "A" 改 "one"

檔案的開頭我們都知道是第一行,但檔案的結尾往往不知是第幾行,此時可用 "$" 來代表最後一行(和 vi 用法一樣,"$" 代表最後一行)。

$ sed '3,$ s/^can/CAN/g' file # 將 3 行到最後一行開頭為 "can" 改大寫

樣板表示法
有起始樣板和結束樣板,每一樣板以成對的 "/" 括起來,起始樣板和結束樣板間以 "," 分隔。

例:位址範圍從有 "The" 到 "When" 之間的行,將字串 "can" 改為 "CAN"

$ sed '/The/,/Whe/ s/ can/ CAN/g' < re.txt

例:將符合 "[Cc]an$" pattern 的那行 "a" 改 "A"

$ sed '/[Cc]an$/ s/a/A/g' < re.txt

混搭表示法
sed 的位址表示法和樣板表示法是可混合使用的。

例:第二行開始到符合 "The" 那行之間任何數字改為字串 "#"

$ sed '2,/The/ s/[0-9]/#/g' < re.txt

例:從有 "google" 那行到最後一行的 "a" 改大寫

$ sed '/google/,$ s/a/A/g' < re.txt

OPTION 選項:

了解 OPTION 選項時先要了解 sed 指令的一些術語,才不會不知所云。sed 的動作為一次只讀一行並去掉結尾的換行(EOL)到暫時的緩衝區(buffer)中,此暫時緩衝區稱為 "pattern space",接著處理完成後會把 pattern space 的內容送往螢幕後清空 pattern space 再去處理下一行,這樣不斷重複直到檔案結束。

如有內容符合搜尋的樣板之 pattern space 叫 "current pattern space",而 COMMAND "p" 或 "P" 可令 sed 再輸出 current pattern space 到螢幕,其基本的動作流程如下。

sed 主要的選項如下:

語法:
sed [-OPTION] [ADD1][,ADD2] [COMMAND] [/PATTERN][/REPLACEMENT]/[FLAG] [FILE]]
指令名稱/功能/命令使用者 選項 功能  
sed/(stream editor)檔案字串修改/Any -e 執行 sed 的 script 語法


-f 選用外部的 script 檔來執行  
-n 不輸出 pattern space 到螢幕  
-l # 和 COMMAND l 一起使用時
l 可以指定每一行的長度
#


-r 使用延伸正規表示  
--help 指令自帶說明  

sed 有些選項可獨力運作,但有些選項單獨使用是沒什意義的,如 -n 或 -l 要配合其他 sed COMMAND 才有意義。各選項說明如下:

  • -e:執行 sed 的 script 語法
    sed 本來就是執行自己專屬的 script 腳本語言,故如都沒選項此為預設選項可省略。但如要搜尋和取代的樣板是多重的,或許多 COMMAND 合併使用,就一定要用 "-e" 選項。

    例如:將 "t" 改為 "T" and "pen" 改為 "pencil"
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'

    例如:許多 sed COMMAND 要使用,就一定要用 "-e" 選項
    $ echo 'this is a pen' | sed -e 's/t/T/' -e 's/pen/&cil/'
  • -f:選用外部的 script 檔來執行
    如 sed 要處理的任務是多重的,此時可把毎一任務一一寫在一起存成一外部 script 檔,再用選項 "-f" 來指定此外部 script 檔即可。這樣做的好處是規則改變時只要改此外部 script 檔即可,或寫許多 script 檔來處理不同的需求。
    例如:有一外部 script 檔 "sed_scr" 功能為把 a~d 改大寫
    $ cat sed_scr
    s/a/A/g
    s/b/B/g
    s/c/C/g
    s/d/D/g
    $ echo 'abcdefg' | sed -f sed_scr
    ABCDefg
  • -n:不輸出 pattern space 到螢幕
    因 sed 處理完成一行後會把 pattern space 的內容送往螢幕後清空,故如單獨使用選項 "-n" 連 pattern space 也不輸往螢幕故輸出什麼也沒有,故選項 "-n" 一般要配合其他的 sed FLAG 使用才有意義。
    一般是配合 sed FLAG "p" 只列出 current pattern space(符合樣板的 pattern),才不會不符合的 pattern 也往螢幕送,此時 sed 的行為可取代 grep。
    例如:選項 "-n" 配合 FLAG "p" 只列出符合樣板的行(類似指令 "grep")
    $ ls -d /etc/* | sed -n '/[A-Z][0-9]/ p'
  • -l:指定每一行的長度
    選項 "-l" (小寫的 L)這選項是配合 COMMAND "l" (小寫的 L)使用,因 sed COMMAND "l" 為把一些 ASCII 控制字元以 "\a"、"\b"、"\t",方式列出,但 ASCII 的控制字元大部分是用來文字定位或排縮,把定位功能取消列印出來可能會影響文字行的長度,故用選項 "-l" 來指定輸出到螢幕的長度。 例如:指定換行的長度=10
    $ echo -e 'This\tIs\tA\tDog' | sed -nl 10 'l'
  • -r: 使用延伸正規表示法解讀
    預設的情形下,sed 的樣板(pattern)只解讀正規表示法,加選項 "-r" 才會去以延伸正規表示法去解讀樣板。此時如樣板內有如 "?","+" 等符號會被認為是延伸正規表示法的 meta-charaters(表示字元)。不同的解讀結果可是差很多。
    例如:正常狀態下,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed 's/99?/88/g'
    Why an apple $9.88
    例如:加選項 "-r" 用延伸正規表示法,將字串 "99?" 改為 "88"
    $ echo 'Why an apple $9.99?' | sed -r 's/99?/88/g'
    Why an apple $88.88?

FLAG 旗幟:

sed FLAG 主要為進一步的控制取代樣板(pattern)的行為,前面我們已用過 FLAG 中的 "g" (global replacement)和 "p" (print)。這兩個 FLAG 是最常使用的,其他的 FLAG 並不常用,了解一下即可,全部的 FLAG 用途如下:

Sed Flags
[g][ 數字] 全部取代或指定取代第幾個
I 忽略 pattern 大小寫
p 列印
w 寫入檔案
  • g:全部取代
    預設 sed 只取代搜尋/取代到第一個樣板此行就停止而去處理下一行,如有g FLAG 則全部搜尋和取代。
    例如:"is" → "IS",但預設只取代搜尋/取代到第一個
    $ echo 'this is an issue' | sed 's/is/IS/'
    thIS is an issue
    例如:加 flag "g" 全部取代
    $ echo 'this is an issue' | sed 's/is/IS/g'
    thIS IS an ISsue
    例如:第 3 個符合的樣本才取代
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3'
    aaAaa aaaaa
    例如:第 3 個符合的樣本才取代,並重複到結束
    $ echo 'aaaaaa aaaaaa' | sed 's/a/A/3g'
    aaAAA AAAAA
  • I:忽略 pattern 大小寫
    此 FLAG 主要對樣板的大小寫一視同仁
    例如:
    $ echo 'this is an apple' | sed 's/APPLE/banana/I'
    this is an banana
  • p:列印 current pattern space
    如沒此 FLAG,sed 只輸出 pattern space。如加了 "p",FLAG 會再輸出 current pattern space。一般此 FLAG 是和選項 "-n" 合作只輸出符合的 pattern (current pattern space)。
    例如:
    
            
  • w:

    例如: