Linux檔案處理三劍客之sed

joshliu發表於2023-11-24

背景

        sed命令最初是由李·麥克馬洪(Lee E. McMahon)在1973年所創造。sed(Stream Editor)是一個流編輯器,用於在Unix作業系統中對文字進行編輯。它允許使用者對輸入的文字進行各種操作,如替換、刪除、插入和其他編輯操作,這些操作可以透過命令列進行控制。sed命令的創造為Unix系統使用者提供了一種強大的文字處理工具,使得對文字進行批次處理和編輯變得更加高效和方便。

    當處理文字或者多行輸入時,sed命令將一行行進行篩選處理,而不會直接跳躍到匹配行。

    

        格式

        sed [OPTION]... {script-only-if-no-other-script} [input-file]...

    其中 :

  •     OPTION為選項

  •     script-only-if-no-other-script為處理動作,可以有-e引數指定多個

  •      input-file為輸入檔案,可以指定多個

         * 注:如果省略input-file,則從標準輸入讀取內容


說明

    sed命令會對輸入檔案或者管道輸入逐行處理,言外之意如果使用sed命令列印檔案的第2行,sed不會直接訪問該行,而是從第1行逐步處理下來,並且也不會處理完第2行之後結束命令,還會將剩餘的內容逐步處理完畢。要真正掌握好sed命令,而不是侷限於“增刪改查”這樣基本的用法,讀者需要了解sed命令中兩個重要概念:

  •     模式空間(pattern space)

  1. 模式空間是sed命令中的主要工作空間,用於儲存、處理當前的文字行。

  2. 每當sed讀取一行文字時,將文字行儲存在模式空間中,sed在模式空間中對文字進行處理和操作。

  3. 模式空間中的文字可以透過命令進行修改、替換、刪除、插入等操作,然後根據需要將最終的文字輸出。

  4. 每處理完一行都會清理模式空間,而後處理下一行。

    *注:如果sed中使用-e執行多個命令,那該行會在所有-e執行完後才會在模式空間中清理掉


  •     保持空間(hold space)

  1. 保持空間是sed命令中的一個輔助空間,用於儲存臨時的資料或文字。

  2. 所有的增、刪、改等操作,只能在模式空間中進行,而不能在保持空間中處理。

  3. 可以透過命令,來將保持空間與模式空間的資料進行互換、追加或者覆蓋(將模式空間的資料追加/覆蓋進保持空間;或者將保持空間的資料追加/覆蓋模式空間)。

  4. 保持空間初始資料為“一個回車”

  5. 保持空間的資料會被持續保留(不會像模式空間處理下一行資料前清空),一旦保持空間與模式空間的資料交換,那保持空間僅儲存互換而來的資料。

    此外,sed命令實際上就是對選中的行進行一系列處理,一種擴充套件格式如下:

sed -n -e '[...] {a1;a2}; a3; a4' -e '[...] {a1; a2; a3}' filename

    [...]表示選擇行(可以不加。如果不加,代表對所有行進行操作),選擇行的方式可以為[xxx , zzz]  /  [xxx, +yyy]  / [ kkk ~ yyy] / [xxx], 其中xxx與zzz可以是數字或正則,但是yyy與kkk只能是數字,部分樣例如下:

  •     [1,/3/]:表示從第1行到第一個匹配到3的行(該匹配不算第1行)

  •     [2,+5]:表示從第2行開始往下5行

  •     [2,5]:表示從第2行到第5行

  •     [1~2]:表示從第1行開始每隔2行直到標準輸入的末尾(包括第1行)

     *注:前後都為閉區間

    關於上述引數-n與-e進一步解釋,請看下一章節。

     此外,

  1.     '[...] {a1; a2; a3}',表示如果滿足[...]中條件,則執行(a1;a2;a3}命令,其它行不執行;

  2.     ' [...] {a1; a2}; a3'表示滿足[...]條件則執行{a1;a2}命令後再執行a3命令,不滿足[...]條件則執行a3命令


引數

    本文僅介紹常見引數,讀者如果有興趣瞭解所有引數,請執行"man sed"或者"sed --help"進一步檢視。

  • -e:同時進行多個操作。預設使用該引數處理一個操作,如果處理多個操作:sed -e '1s/p/|/g' -e '5s/q/|/g' a.txt

    *注:如果使用多個-e處理多個操作,則第一個-e也不能省略

  • -i:直接對輸入檔案進行操作(慎用,驗證後再決定是否使用)

  • -n:抑制“ 預設對模式空間的輸出”,如果需要輸出到終端,請在sed中使用p/P顯式列印

    *注:如果沒有-n選項,sed命令預設會在處理完每一行後自動將模式空間的內容輸出到標準輸出上

  • -E或-r:支援擴充套件正規表示式,通常的擴充套件如下:

  1. +:重複1次或者多次前面的字元

  2. ?:重複0次或者1次前面的字元

  3. |:用“或”的方式查詢多個符合的字串

  4. ():作為某個特定組合,比如說sed -E '/level=(fail|error)/p' file,表示在file中查詢包括level=fail或者level=error的內容。如果去掉括號sed -n -E '/level=fail|error/p' file,代表查詢匹配level=fail或者一行中單獨匹配error的內容

  5. {m,n}/{m,}/{,n}/{n}:表示重複m到n次(閉區間)/大於等於m次/小於等於n次/n次,在不用-E引數時,所有這部分花括號用法得兩側加反斜線:\{m,n\}


內建命令(第一部分)

    本文也僅介紹常用的sed內建命令,如果想進一步瞭解,請執行"man sed"。

  • i:將指定的文字在輸出時新增到當前行的前面,但不會追加/覆蓋到模式空間。比如sed '1i abcd' a.txt是在處理a.txt第1行的時候,將文字abcd在輸出時候插入到第1行前面。

  • a: 將指定的文字在輸出時追加到當前行的後面,也不會 追加/覆蓋到模式空間。

  • c:先清空模式空間的資料,再將指定的文字輸出到終端。

  • p:列印當前模式空間中的資料,一般與-n一起使用

  • q:退出sed命令,不處理輸入中剩餘內容。如果沒有-n選項,會列印模式空間中資料

  • Q:退出sed命令,不處理輸入中剩餘內容。不管有沒有-n選項,都不會列印模式空間中資料

  • d:刪除當前模式空間中的資料

  • s:對當前模式空間內容進行替換 's/正則/文字/'

    *注:為了更方便sed中的替換,所以s命令本身也有幾個引數,擴充格式為:'s/正則/文字/[引數]',其中引數有:

    數字:表示對一行中第幾個出現的匹配項進行替換(如果命令要求對第2個匹配abc的部分進行替換,但該行只有1個abc,那不會進行替換)

    i:ignore,忽略大小寫

    g:global,對整行資料所有匹配內容都進行替換。如果不加g,預設只會對第一個匹配內容進行替換。

    p:print,對模式空間修改後的內容進行列印

    w:w filename,將模式空間修改後的內容寫入到filename中

  • r:將指定檔案的內容追加到輸出中,但不會追加/覆蓋到模式空間。比如sed '1r filename' a.txt是在處理a.txt第1行時候,將filename的內容輸出到終端。

  • w:將模式空間的內容寫入到某個檔案中,比如sed '2w filename' a.txt代表將第二行資料寫入到filename中

  • y:y/acbd/ACBD/,在當前模式空間中將a替換成A,將c替換成C,將b替換成B,將d替換成D,比如echo 'ab' | sed 'y/bcad/BCAD/'的輸出是AB


用法介紹(第一部分)

  • 在a.txt的第2行前加上'xxx',第4行後加上'yyy',並且最後一行替換成'xyz'

    首先a.txt中內容如下:

    cat a.txt  

    xxx1 xxxa

    xxx2

    xxx3 xxb

    test1

    xxx2

    xxx4

    xxx3

    test2 xxcxx

    xxx33 xxx44

    xxx2

    xxx5

    sed命令如下:

sed -e '2i xxx' -e '4a yyy' -e '$c xyz' a.txt

    


  • 刪除第2行資料到匹配test的資料

sed '2,/test/d' a.txt


  • 將a.txt的第5行中2替換成22,並列印替換後資訊

sed -n '5s/2/22/p' a.txt

    

     *注,如果不使用-n引數與內建p命令,結果如下:

    


  • 列印匹配/xxx2/到匹配/xxx3/的資料

    讀者如果觀察原始檔,會發現內容中有好幾個匹配/xxx2/與/xxx3/的行,那麼,到底sed是怎麼處理的呢?

    假如直接寫命令 sed -n '/xxx2/,/xxx3/p' a.txt,是匹配第一個/xxx2/到第一個/xxx3/還是第一個/xxx2/到最後一個/xxx3/,還是每滿足一次/xxx2/到/xxx3/都列印出來?

    我們先看上述命令的執行結果:

    

    基於上述結果,我們可以猜測首先不是列印的第一個/xxx2/到第一個/xxx3/,也很明顯不是列印的第一個/xxx2/到最後一個/xxx3/,看起來像是每碰到一次/xxx2/與/xxx3/都會把之間的內容列印出來,但為何會列印了最後一行xxx5呢?

    所以猜測對於[xxx,yyy],如果yyy不存在或者在xxx後不匹配,那代表是xxx開始往下的所有行都會進行處理。為了驗證這一點,比如說我們列印a.txt中匹配/xxx2/與/abcdef/(不匹配/xxx2/後任一行)之間的行,看結果會如何。

    

    果然,將第一個/xxx2/的行一直到最後一行的資料,都列印了出來。我們再換一種,列印從第7行開始直到匹配/abcdef/的行:

    

    同樣,如果執行sed -n '/xxx2/,/xxxa/p' a.txt

    

    上述案例中,儘管/xxxa/出現在了第1行,但因為/xxx2/最早出現在第2行,也就是第二個正則在第一個正則匹配到的行的下方不能匹配到行(儘管在上方能匹配一行),這種情況下sed仍然會處理從第一個匹配/xxx2/的行到最後一行。

    所以透過如上幾個案例,也基本證實了最開始的猜測,如果透過[數字/正則, 正則]形式取行,假如第二個正則如果不存在,那命令會持續到文字結束。不過對[a,+b]或者[a~b]這兩種取行而言,不存在此種混淆,因為b只能是數字,沒有可能匹配到多行的情況。

    但需要注意的是,如果sed -n '3,1p' a.txt,處理的是第3行資料,而不是從第3行開始往下所有行的資料。

    

    我們將該部分內容總結如下:

    1. [數字]:不會混淆,僅針對數字指定的行數進行處理。如果數字大於標準輸入的行數,那sed不會對任何行進行處理(但會掃描標準輸入的每一行)*注:數字大於等於1,下同

    2. [正則]:不會混淆,僅針對正則能匹配到的所有行進行處理。如果正則匹配不到任何行,那sed不會對任何行進行處理(但會掃描標準輸入的每一行)

    3. [數字1, 數字2]:不會混淆,僅針對數字1到數字2之間的行進行處理(閉區間)

    • 如果數字1大於標準輸入的行數,那sed不會對任何行進行處理(但會掃描標準輸入的每一行)

    • 如果數字1小於等於標準輸入的行數,但數字2大於標準輸入的行數,那sed處理從數字1到標準輸入的最後一行資料

    • 如果數字2小於等於數字1,那sed不會報錯,並且只會處理數字1對應的行

    4. [數字, 正則]:可能混淆,僅針對數字到下面 第一個正則匹配到的行進行處理(而不管該正則可以匹配多少行)

    • 如果數字大於標準輸入的行數,那sed不會對任何行進行處理(但會掃描標準輸入的每一行)

    • 如果數字小於等於標準輸入的行數,但正則從數字行以後匹配不到任何行,那sed處理從數字行到標準輸入的最後一行資料

    • 如果數字小於等於標準輸入的行數,但正則從數字行以後可以匹配到若干行, 那sed處理從數字行到正則匹配到的第一行資料

    5. [正則, 數字]:可能混淆,因為正則可能會匹配到多行,我們從下述樣例進行說明:

            

    • 首先看sed -n '/xxx3/,1p' b.txt的結果,起點是正則,尾點是數字,正則在b.txt中對應了第4、7、10行,數字對應第1行。根據上述[數字1, 數字2]類比,如果數字2(尾點)在起點上面,那隻會處理起點的行。在本例中,起點是正則,匹配了3行,所以sed會輪詢處理這3行,並且由於尾點為1,所以每次輪詢都只會輸出本輪起點的行,也就是為什麼螢幕上輸出了3個xxx3。

    • 再來看sed -n '/xxx3/,5p' b.txt的結果,起點是正則,尾點是數字,正則對應第4、7、10行,數字對應第5行。所以第一個輪詢是從[4,5],也就列印xxx3與xxx4;第二次輪詢是[7,5],也就列印了第二個xxx3;第三次輪詢是[10,7],所以列印了第3個xxx3。

    • 接著看sed -n '/xxx3/,8p' b.txt,起點仍然是正則,尾點仍是數字,正則對應第4、7、10行,數字對應第8行。所以第一個輪詢是[4,8],也就列印了xxx3、xxx4、test1、xxx3、xxx5行,此時已經處理完第8行了,所以正則的第二次輪詢從再下一個匹配的第10行開始,也就是[10,8],所以繼續列印最後匹配的那個xxx3。

    • 最後看sed -n '/xxx3/,108p' b.txt,起點、尾點、正則對應行與前述一致,數字對應第108行(超過了標準輸入的行數10),第一次輪詢處理[4,108]也就是從第4行開始直到最後一行資料,此時也不再需要後續的輪詢。

    6. [正則1, 正則2]:可能混淆,不過如果讀者把該類場景也聯想成起點與尾點的輪詢,那所有的類比都是相似的。

    簡單總結下,對於[x,y]型獲取行的場景:

    為便於深入掌握,請解釋下面命令的結果:

    

     *注:cat -n為列印行號顯示;第5行正好為/xxx4/對應行。


內建命令(第二部分)

    在內建命令的第一部分,我們著重介紹了與“模式空間”相關的引數,下面介紹一些與保持空間相關的引數,並額外補充部分模式空間引數。

  • n:將下一行資料複製到模式空間,並且之後不會對下一行資料再次掃描

  • N:將下一行資料追加到當前模式空間(變為兩行,回車作為換行符),並且之後不會對下一行資料再次掃描

  • P:列印模式空間第一行資料(p代表列印模式空間所有資料)

  • h:將模式空間的資料複製到保留空間

  • H:將模式空間的資料追加到保留空間

  • g:將保留空間的資料複製到模式空間

  • G:將保留空間的資料追加到模式空間

  • x:互換模式空間與保留空間的資料

  • z:將模式空間的資料換成\n

  • b: {a1;b;a2;a3;a4},在執行完a1後,直接跳到最後(不執行a2,a3,a4)

     *注:上述複製都是覆蓋型複製

    所以在有了保持空間之後,sed命令變得更加強大了,不僅能處理單行資料,還能處理多行資料。我們繼續使用樣例來進行說明。


用法介紹(第二部分)

  • 列印a.txt的第2,4,10行

#方法一:使用grep獲取行號,再選擇第2/4/10行,而後將對應的內容輸出
grep -n '' a.txt | grep -E '^(2|4|10):' | cut -f2 -d:
#方法二:使用sed -n -e的方式,透過多個-e來進行輸出
sed -n -e '2p' -e '4p' -e '10p' a.txt
#方法三:使用sed -n外加多個分號直接輸出
sed -n '2p;4p;10p' a.txt
#方法四:使用保持空間,第2行的時候將模式空間的資料複製到保持空間,第4行追加到保持空間,在第10行的時候追加到保持空間,而後再將兩個空間交換,最後列印
sed -n '2h;4H;10{H;x;p}' a.txt
#方法五:使用awk的NR引數,將對應的行選出後列印
awk 'NR == 2 || NR == 4 || NR == 10' a.txt

    


  • 將a.txt第3行的資料插入到第8行下

#方法一:直接使用awk,將第3行資料儲存到某個變數,並且第8行輸出當前行+回車+該變數,其餘行正常輸出
awk '{if(NR==3){a=$0};if(NR==8){print $0"\n"a} else {print}}' a.txt
#方法二:使用sed,將第3行資料獲取到作為另一個引數傳入新的sed
sed "8a $(sed -n '3p' a)" a.txt
#方法三:使用sed,將第3行資料先複製進保持空間,並在sed處理第8行時,將保持空間資料追加到當前模式空間第8行下
sed '3h;8G' a.txt


  • 每一行後加上回車再輸出

#方法一:使用awk,每次列印當前行+回車
awk '{print $0"\n"}' a.txt
#方法二:sed進行s///替換
sed 's/$/\n/' a.txt
#方法三:sed直接使用a命令追加空行,這裡“空行”在a命令追加時,請使用兩個反斜槓表示
sed 'a \\' a.txt
#方法四:將保持空間直接追加到當前模式空間(保持空間預設就是換行)
sed 'G' a.txt


  • 輸出偶數行

#方法一:使用awk
awk '{if(NR%2==0) {print}}' a.txt
#方法二:仍使用awk,但簡化上述命令
awk 'NR%2==0' a.txt
#方法三:使用sed,從第2行開始,每隔2行輸出
sed -n '2~2p' a.txt
#方法四:利用模式空間命令n,直接跳躍到下一行(將下一行復制到模式空間)
sed -n 'n;p' a.txt


  • 將相鄰行合併成一行輸出,行內使用'|'分隔

#方法一:使用awk,奇數行只輸出自己,偶數行輸出|+自己+回車
awk '{if(NR%2==1){printf "%s",$0} else {printf "|%s\n",$0}}' a.txt
#方法二:使用sed以及-n引數
sed -n '$!N;s/\n/|/;p' a.txt #$!N代表如果不是最後一行則將下一行資料追加到當前模式空間。這樣能保證如果總行數是奇數行時,最後一行也能被列印出來
#方法三:改進上述sed,去掉-n引數
sed 'N;s/\n/|/' a.txt

  • 如何取出/abc/的行(忽略大小寫)

sed -n '/abc/Ip' a.txt #在s///中,文章上方有介紹過一些引數,但是透過正則匹配若干行時候,如果忽略大小寫,請使用I引數


  • 除了第3與第8行,其餘行下新增文字內容"xxx"

#方法一:使用awk
awk '{if(NR == 3 || NR == 8){print} else {print $0"\n""xxx"}}' a.txt
#方法二:使用兩次sed
sed 'a xxx' a.txt |sed '6d;16d'
#方法三:使用sed的b引數,如果是第3或者8行,直接跳過當前行
sed -n 'p;3b;8b;a xxx' a.txt


  • 假如檔案內容如下,如何取beijing的同事

    [root@host46 ~]# cat name

    name1

    shanghai

    name2

    shanghai

    name3

    tianjin

    name4

    beijing

    name5

    beijing

    name6

    guiyang

    name7

    guiyang

#方法一:直接grep加B引數,取出beijing的上一行,再使用管道處理
grep -B1 beijing name |grep -v beijing
#方法二:使用sed將兩行合併成1行,然後再做篩選
sed -n '$!N;s/\n/|/;p' name | grep 'beijing'|cut -f1 -d\|
#方法三:使用awk
awk '{if (NR%2==1){a=$0} else {if($0 ~ /beijing/){print a}}}' name
#方法四:使用sed,先將模式空間與保持空間交換,再取下一行到模式空間,如果下一行匹配北京,則把保持空間內容重新交換回來後列印
sed -n 'x;n;/beijing/{x;p}' name
#方法五:還是使用sed,直接將下一行追加到模式空間中,如果模式空間匹配北京,則列印模式空間上面一行(即為姓名)
sed -n 'N;/beijing/P' name

  


其它用法

    sed命令還提供了一些其它用法如下:

  •     $:最後一行

  •     !:不匹配,比如說sed -n '2!p' a.txt,代表不是第2行則列印;或者sed -n '/abc/!p' a.txt代表不匹配abc則列印

  •     &:sed透過s進行替換時,前一部分正則匹配到的內容

  •     ():sed(使用-E引數)透過s進行替換時,如果使用小括號,則該部分內容在後續可以使用\1代替;如果有兩個小括號,響應使用\2代替

  •     \U:替換成大寫

  •     \I:替換成小寫

  •     \u:首字母替換成大寫

  •     \i:首字母替換成小寫

    比如將a.txt轉化為大寫,可以使用sed 's/[a-z]/\U&/g' a.txt

    如果只是將a.txt第3行出現的第一個xx替換成大寫,並只輸出替換行,可以使用sed -n '3s/xx/\U&/p' a.txt

    如果將a.txt中,連續小寫字母與連續數字進行對調,可以使用sed -n -E 's/([a-z]+)([0-9]+)/\2\1/gp' a.txt

    


總結

    sed作為Linux/Unix上上強大的文字處理工具,提供了多種引數供使用者選擇,並且內部也有各種各樣命令來實現對行內容的各種處理。同時,sed還有兩種空間:模式空間與保持空間,使用者可以自由操作並且切換這兩種空間來實現更加複雜的檔案增刪改查等功能而避免了編寫指令碼的過程。另外,sed在不特別指定情況下,會逐行處理檔案,而不會處理到指定行就結束。指定行時,sed可以透過行號或者正則來完成,並且可以選所有行/部分行/單選一行,或者從某一行到某一行,再或者某一行往下幾行,還有從某一行每隔幾行等方式。

    總之,sed非常強大並且完備,需要多加練習與嘗試才能得心應手。此外,sed還有一些其它引數,像是-f,或者其它命令,包括label等方面,本文對這部分不做額外介紹,如果讀者有興趣瞭解,可以另行查閱資料學習。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69985104/viewspace-2995367/,如需轉載,請註明出處,否則將追究法律責任。

相關文章