# sed

教學網站

- [http://www.tecmint.com/linux-sed-command-tips-tricks/](http://www.tecmint.com/linux-sed-command-tips-tricks/)
- [https://www.cyberciti.biz/faq/how-to-use-sed-to-find-and-replace-text-in-files-in-linux-unix-shell/](https://www.cyberciti.biz/faq/how-to-use-sed-to-find-and-replace-text-in-files-in-linux-unix-shell/)
- [Complete Sed Command Guide](https://linuxhandbook.com/sed-reference-guide/)
- [Sed - An Introduction and Tutorial by Bruce Barnett](https://www.grymoire.com/Unix/Sed.html)
- [Learn to use the Sed text editor](https://opensource.com/article/20/12/sed)
- [THE SED FAQ](http://www.pement.org/sed/sedfaq.html)
- [Useful sed](https://github.com/adrianscheff/useful-sed)
- [sed 進階範例](https://www.thegeekstuff.com/category/sed/)

##### 分隔符號

除了常見的 `/` ，也能使用 `@` , `|` , `%`

> Tip: 有時遇到要搜尋某些特殊符號，可能會需要更換不同的分隔符號。

For `s` command

```bash
# 將 /usr/bin 加上 local 成為 /usr/bin/local
sed 's@/usr/bin@&/local@g' path.txt
```

Other command

```bash
# 開頭要加一個反斜線
echo $PATH | sed -n '\@/usr/local/sbin@p'
```

##### 常用語法範例

```shell
# 移除註解, 空白行
sed '/^#\|^$\| *#/d' my.conf
# 只移除註解文字，但不刪除整行
sed 's/#.*//' file.txt

# 搜尋取代: 每一行第 1 個 Linux 換成 Unix
sed 's/Linux/Unix/' linuxteck.txt
# 搜尋取代: 每一行第 1 個 Linux 換成 Unix (不分大小寫)
sed 's/Linux/Unix/i' linuxteck.txt

# 搜尋取代: 每一行第 2 個 Linux 換成 Unix
sed 's/Linux/Unix/2' linuxteck.txt
# 搜尋取代: 第二行第 1 個 Linux 換成 Unix
sed '2 s/Linux/Unix/' linuxteck.txt
# 搜尋取代: 全文的 Linux 換成 Unix
sed 's/Linux/Unix/g' linuxteck.txt

# 列印: 行號
sed -n p linuxteck.txt
# 列印: 第 2-4 行
sed -n '2,4p' linuxteck.txt
# 列印: 移除第 2-4 行
sed '2,4d' linuxteck.txt
# 列印: 第 2-3, 5-6 行
sed -n -e '2,3p' -e '5,6p' linuxteck.txt
# 列印: 包含 operating 的行
sed -n /operating/p linuxteck.txt

# 每一行之間加一個空白行
sed G linuxteck.txt
# 在每個註解行下方加一個空行
sed '/^#/G' file.txt

```

##### 列出搜尋內容

```shell
# 列出第 100 行以下的所有文字
# 若要刪除，可將 p 換成 d。
sed -n '100,$p'  my.txt

# 列出第 3 行以上的所有文字
sed '3q' my.txt

# 列出第 130 行
sed -n '130{p;q}' my.txt

# 列出第 100 - 130 行文字
sed -n '100,130p' my.txt
sed -n '100,130p;130q' my.txt

# 列出關鍵字 all 以下所有行
sed -n '/all/,$p' my.txt

# 列出關鍵字 start 以上的所有文字
sed '/start/q' file.txt

# 搜尋關鍵字，並列出這個段落的所有文字
sed -n '/keyword/,/^$/p'

# 列出關鍵字 Top 與 Bottom 之間的所有文字
sed -n '/Top/,/Bottom/p'

# 搜尋字串的那一行
sed -n "/PATTERN/p" my.txt
sed -n "/PATN1\|PATN2\|PATN3/p" my.txt
```

##### 刪除搜尋內容

```shell
# 刪除行首為# 的行
sed '/^#/d' 

# 刪除行首為 # 或空白的行
sed '/^#\|^$/d'
sed '/^#/d;/^$/d' # for AIX

# 刪除多個關鍵字的行, ADMIN_ERP_ROLE, RPT01,...
sed '/ADMIN_ERP_ROLE/d;/RPT01/d'

# 刪除空白行
sed '/^$/d'
sed '/^\s*$/d'

# 刪除第 42 行文字
sed 42d my.txt

# 刪除空格
sed 's/[[:space:]]//g' mywords
sed 's/^[ \t]*//;s/[ \t]*$//' mywords
sed 's/^\s*\|\s*$//g' mywords

# 刪除所有行尾的空格字元
sed 's/[[:space:]]*$//'

# 刪除行尾的 ^M (CR 字元)
sed 's/^M//g'      NOTE: to get ^M type CTRL+V followed by CTRL+M
sed 's/\r$//g'

# 刪除 iXXXX 的帳號, XXXX 是數字
sed '/^i[0-9]*/d' mypasswd

# 刪除最後一行
sed '$d' my.txt
```

##### 搜尋後取代/插入文字

```shell
# 在第 2 行的上方，插入文字 xxx
sed '2i xxx' my.txt

# 在第 2 行的下方，插入文字 yyy
sed '2a yyy' my.txt

# 在第二行的行尾，插入文字 ****
sed '3 s/$/ ****/'

# 搜尋最後一行的 }, 取代成 }
sed -i '$ s/},/}/' "$OUTPUT_JSON_FILE"

# 在搜尋的行下方新增字串 newstring
sed '/patterm/a\newstring' my.txt

# 在所有文字的行首加上 #
sed 's/^/# /' my.txt

# 在所有文字的行尾加上 End
sed 's/$/ End/' my.txt

# 將每一行裡是兩個以上空白字元, 都換成一個 comma 符號
sed "s/  \+/,/g"

# 快速取代字串
sed 's/old/new/' mywords
sed 's|old|new|' mywords

# 搜尋關鍵字 'astlogdir =>' 的這一行，取代行字串為 'astlogdir => /mnt/usb/asterisk_log'
# .*$ 表示行尾前的所有字串
sed -i 's/astlogdir =>.*$/astlogdir => \/mnt\/usb\/asterisk_log/g'

# 搜尋 'none'，並且在該行行首加上 '#'
sed 's/none/#&/g' mywords 

# 在字串  'daemon' 的下方插入另一個檔案 3.txt 的內容
sed '/daemon/r 3.txt' mywords 
sed '/INCLUDE/r foo.h' sample.c

# 特殊字元 / \ & 前加上跳脫符號
sed 's/[\/&]/\\&/g' mywords

# 搜尋文字 'daemon'，將符合的內容寫到檔案 3.txt
sed -n '/daemon/w 3.txt' 1.txt 

# 將符合文字 'root' 內容的下一行，插入特定的文字 'test test'
[root@linux-3 ~]# sed ‘/root/a test test’ 1.txt
root:x:0:0:root:/root:/bin/bash
test test
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 

# 將符合文字 'daemon' 內容的上一行，插入特定的文字 'test test'
[root@linux-3 ~]# sed '/^daemon/i test test' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
test test
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

# 搜尋所有文字是 'ext3' 的，用 [ ] 框起來
sed 's/ext3\+/[&]/g' my.txt

# 搜尋文字並取代部份的內容
sed -i 's/^\(User\|Group\).*/\1 asterisk/' /etc/httpd/conf/httpd.conf

# 搜尋開頭是 revoke 的每一行尾，加上分號. & 表示符合搜尋的內容
sed 's/^revoke.*$/&\;/' revoke_winieop.sql

# 搜尋每一行, 並格式為 <<<STRING>>>. "^.*$" 表示每一行, & 表示符合搜尋的內容
sed 's@^.*$@<<<&>>>@g' path.txt

# 將 /usr/bin 加上 local 成為 /usr/bin/local
sed 's@/usr/bin@&/local@g' path.txt
```

備份 \*.sh 檔案且移除第一行 `#!/bin/bash`

```shell
sed -i.bak '1i #!/bin/bash' edit.sh
```

執行多個規則

```shell
sed -e '/^#/d' -e '/^$/d' 
```

找出關鍵字的右側字串內容

```
$ cat cookie.txt
xxxx  FALSE   /       FALSE   0       PHPSESSID       dbbr5nsmib9tgm0h97sbq8ovd0

$ cat cookie.txt | sed 's/^.*PHPSESSID[ \t]*//' 
dbbr5nsmib9tgm0h97sbq8ovd0
```

找出行的左側內容  
列出 10.14.25.196 與 GA016E38 (Hex IP)

```
$ cat input.txt
 
 GA160223.MAE3.219594040120
10.14.25.196.49611.200203080358
GA016E38.O5FA.259D81215343
10.4.1.29.41266.200114031620
GA12640A.M6EE.20EDC3093010

$ sed 's/^\(.*\)\.[A-Z0-9]*\.[A-Z0-9]*/\1/' input.txt

GA160223
10.14.25.196
GA016E38
10.4.1.29
GA12640A
```

對 CSV 檔案的內容，將所有分隔符號 comma 置換成 @@，但必須排除有包含 comma 的文字敘述，這些文字內的 comma 前個字元會有個空白

```shell
sed 's/\(,\)\([^ ]\)/@@\2/g' orig.csv
```

> s 搜尋
> 
> \\( \\) \\( \\) 用括弧區分兩個字元
> 
> \[^ \] 非空白的字元
> 
> \\2 第二個字元，這裡要配合括弧的用法
> 
> g 作全文置換，若沒有此參數，預設只會置換第一個符合的字元

搜尋 A.AA B.BB C.CC 3個數值，並以指定的格式輸出

```shell
sed 's/^\([0-9]\+\.[0-9]\+\) \([0-9]\+\.[0-9]\+\) \([0-9]\+\.[0-9]\+\).*$/1-minute: \1\n5-minute: \2\n15-minute: \3/g' /proc/loadavg
```

> ^\\(\[0-9\]\\+\\.\[0-9\]\\+\\) \\(\[0-9\]\\+\\.\[0-9\]\\+\\) \\(\[0-9\]\\+\\.\[0-9\]\\+\\).\*$ 搜尋語法
> 
> 1-minute: \\1\\n5-minute: \\2\\n15-minute: \\3 輸出語法

搜尋/驗證 IP 位址

```bash
sed -n '/\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}/p' my.txt
```

取代所有的 mailbox = xxx 為 mailbox = xxx@context

```shell
sed 's/^mailbox = [0-9]*/&@context/g' users.conf
```

搜尋特定區段的 AllowOverride None

> 只取代這個區段所包含的關鍵字  
> &lt;Directory "/var/www/html"&gt;  
> ...  
> &lt;Directory&gt;

```shell
sed -i ':a;N;$!ba;s/AllowOverride None/AllowOverride All/2' /etc/httpd/conf/httpd.conf
```

擷取 { } 所包含的所有文字

> &lt;br/&gt; &lt;b&gt;Notice&lt;/b&gt;: Undefined variable: sn in &lt;b&gt;/var/www/raida/service/fix.php&lt;/b&gt; on line &lt;b&gt;259&lt;/b&gt;&lt;br/&gt; {"server":"RAIDA17","sn":"","status":"success","message":"Fixed: Unit's AN was changed to the PAN. Update your AN to the new PAN.","time":"2017-04-08 06:08:25"}

```shell
echo $( cat http_get.txt ) | sed  's/.* \({.*}\)$/\1/'
```

擷取 \[ \] 內的文字

```shell
echo "[1070059:1,1070060:1,1070039:1]" | sed  's/^\[\(.*\)\]$/\1/'
```

##### 排除法搜尋

```shell
# 移除所有行尾結束字元不是雙引號的
sed '/\"$/!d'

# 移除所有行開頭不是 "2021-09-26-23.13.02.097806" 這樣格式的內容
sed '/^\"[0-9]\+\-[0-9]\+\-[0-9]\+\-[0-9]\+\.*/!d'

# 排除 PATTERN1 + 搜尋 PATTERN2 + 取代 WORDS
sed '/PATTERN1/!s/PATTERN2/WORDS/g'

# 除第5行以外的所有文字,搜尋 foo 並以 bar 取代
sed '5!/s/foo/bar/' file.txt

# 排除 start 到 end 之間段落的所有文字
sed -rn '/start/,/end/ !p' file.txt
```

##### 段落搜尋

```shell
# 列出關鍵字 Top 與 Bottom 之間段落的所有文字
sed -n '/Top/,/Bottom/p'

# 在 start 與 end 之間段落，搜尋井字開頭的文字，並刪除；井字前的文字仍會保留
sed -E '/start/,/end/ s/#.*//' file.txt
```

##### 進階應用範例  


將數字 123456 格式化成 123,456

```bash
echo "123456" | sed 's/\(^\|[^0-9.]\)\([0-9]\+\)\([0-9]\{3\}\)/\1\2,\3/g'
```

(W)elcome (T)o (T)he (G)eek (S)tuff

```bash
echo "Welcome To The Geek Stuff" | sed 's/\(\b[A-Z]\)/\(\1\)/g'
```

Get the list of usernames in /etc/passwd

```bash
sed 's/\([^:]*\).*/\1/' /etc/passwd
```

變數資料

```bash
sed -n '\@/usr/local/bin@p' <<< $PATH
```

將群組清單以行方式列出

> Tip: `( ) - +` 等符號需要加上跳脫字元

```bash
groups

# domain users@winfoundry.com vdi_fc_it_std01@winfoundry.com pab_zwin_all2@winfoundry.com mis@winfoundry.com prtg_mfgs-r@winfoundry.com 1-3_vpn_it@winfoundry.com

groups | sed 's/\([a-z0-9_ \-]\+@winfoundry.com \)/\1\n/g'

#domain users@winfoundry.com
#quota@winfoundry.com
#it-w@winfoundry.com
#iso 14001-r@winfoundry.com
#mis@winfoundry.com
#it@winfoundry.com
#cqa_spc-r_old@winfoundry.com
#fab_image-r@winfoundry.com
#hp4155b@winfoundry.com
#1-3_vpn_it@winfoundry.com
```

##### Markdown 內容

設定 "第 X 章" 為標題 Level 5 格式

```bash
# Search: **第 一 章 總則**
# Replace: ##### 第 一 章 總則

sed 's/^.*\(第\ .*\ 章 .*\)\*\*.*$/##### \1/' markdown/勞動基準法.md

# Serch: **法規名稱：**性別平等工作法
# Replace: #### 法規名稱：性別平等工作法

sed -i 's/^\*\*\(法規名稱：\)\*\*\(.*\)$/#### \1\2/' markdown/勞動基準法.md
```

設定 "第 X 條" 為標題 Level 6 格式

```bash
# Search: **第 1 條**
# Replace: ###### 第 1 條

sed 's/^.*\(第\ .*\ 條\).*$/###### \1/' markdown/勞動基準法.md
```

修正不正確的斷行

```
1
要派單位不得於派遣事業單位與派遣勞工簽訂勞動契約前，有面試該派遣勞工或其他指定特定派遣勞工之行為。

2
要派單位違反前項規定，且已受領派遣勞工勞務者，派遣勞工得於要派單位提供勞務之日起九十日內，以書面向要派單位提出訂定勞動契約之意思表示。
```

修正後

```
1 要派單位不得於派遣事業單位與派遣勞工簽訂勞動契約前，有面試該派遣勞工或其他指定特定派遣勞工之行為。

2 要派單位違反前項規定，且已受領派遣勞工勞務者，派遣勞工得於要派單位提供勞務之日起九十日內，以書面向要派單位提出訂定勞動契約之意思表示。
```

```bash
sed 'N;s/^\([123456789]\)\n/\1 /' markdown/勞動基準法.md
```