text / sed === ###### tags: `OS / Ubuntu / cmd / text` ###### tags: `OS`, `Ubuntu`, `linux`, `command`, `text`, `sed` <br> [TOC] <br> :::info ### text / sed - GNU sed - sed 是屬於 **文字處理與編輯工具(stream editor for text)** - 用途為:**基於模式(pattern)的行級處理與取代** - 適合處理:**複雜的文本操作**,例如條件取代、正則篩選、批次修改文字、多行操作等 ::: <br> --- ## 快速導覽情境 | 情境 | 用法 | |-----|------| | 擷取 yaml 某個設定(某一行到某一行) | `sed -n '/^crds:/,/^[^ ]/p'` | | 擷取第2至5行 (某一行數到某一行數) | `cat content.txt \| sed -n '2,5p'` | | 擷取第3行 (某一行數) | `cat content.txt \| sed -n '3p'` | | 擷取第3行到最後 (某一行數到結尾) | `cat content.txt \| sed -n '3,$p'` | ### 常見參數 - `-n, --quiet, --silent` suppress automatic printing of pattern space <br> --- ## 參數 `-n` 效果 > - `-n, --quiet, --silent` > suppress automatic printing of pattern space - ### `cat test.txt` ``` 1 22 333 22 1 ``` - ### `cat test.txt | sed -n '1,3p'` ``` 1 22 333 ``` - ### `cat test.txt | sed '1,3p'` ``` 1 1 22 22 333 333 22 1 ``` <br> --- ## [案例] 擷取 yaml 某個設定(某一行到某一行) ``` $ helm show values oci://quay.io/jetstack/charts/cert-manager --version v1.17.1 \ | sed -n '/^crds:/,/^[^ ]/p' Pulled: quay.io/jetstack/charts/cert-manager:v1.17.1 Digest: sha256:be0fbcfc39960d0393d6cad446fce654f3e6a8ee5a44bc7d601dda6813f230e0 crds: # This option decides if the CRDs should be installed # as part of the Helm installation. enabled: false # This option makes it so that the "helm.sh/resource-policy": keep # annotation is added to the CRD. This will prevent Helm from uninstalling # the CRD when the Helm release is uninstalled. # WARNING: when the CRDs are removed, all cert-manager custom resources # (Certificates, Issuers, ...) will be removed too by the garbage collector. keep: true # +docs:section=Controller ``` <br> --- ### 導讀 ``` sed -n '/^crds:/,/^[^ ]/p' ``` - **開始行**:`/^crds:/` - **結束行**:`/^[^ ]/` - **範圍**:`,` -> 建立從「開始行」到「結束行」的範圍 - **功能**:`p` -> print(print the pattern space) <br> --- ### `sed -n '/^crds:/,/^[^ ]/p'` 這指令如何解釋? - ### gpt-5 (2025/10/02) 這是一個用來「擷取一段區塊」的 `sed` 指令,常拿來從 YAML/Helm values 抽出某個頂層 key 的內容。 **指令:** ```bash sed -n '/^crds:/,/^[^ ]/p' values.yaml ``` **逐段解釋:** * `sed -n`:關閉預設自動輸出(silent mode)。之後只有被明確 `p` 的行才會印出。 * `'/^crds:/,/^[^ ]/p'`:這是「位址範圍(address range)」+ 動作 `p`。 * `^crds:`:起點。匹配**行首**就是 `crds:` 的那一行(通常是 YAML 的頂層 key)。 * `^[^ ]`:終點。匹配**行首第一個字元不是空白**的那一行——也就是「下一個頂層行」(下一個 section 的開頭、或任何沒縮排的行,如 `image:`、或行首為 `#` 的註解)。 * `p`:把**從起點到終點(含兩端)**之間的所有行印出。 **它實際會做什麼?** * 從第一個 `crds:` 那行開始,**一路印到下一個「不以空白開頭」的行**(包含那一行)。 * 用在 YAML 時,就是把 `crds:` 區塊整段印出,**但會順便把下一個頂層 key 那行也帶出來**。 **小範例:** 內容: ```yaml crds: enabled: true keep: false image: repository: jetstack/cert-manager ``` 輸出會是: ``` crds: enabled: true keep: false image: ``` > 注意最後一行 `image:` 也被印出,因為 range 是「含終點」。 --- ### 想避免把「下一個頂層 key」那行印出? 最簡單做法是把最後一行丟掉: ```bash sed -n '/^crds:/,/^[^ ]/p' values.yaml | sed '$d' # 或: … | head -n -1 ``` - 不能用 `sed "$d"` ### 常見眉角 * `^[^ ]` 等同「行首不是空白」的行;在 POSIX `sed` 沒有 `\S`,所以常用否定字元類別寫法。 * 若 `crds:` 區塊後面緊接著**行首註解**(如 `# …`),那一行也會被當成「終點」而被包含。 * YAML 規範用**空白**縮排;若資料夾雜了 tab(不建議),tab 不是空白字元,`^[^ ]` 也會把它當成「終點」。 ### 更「像 YAML key」的終點寫法(可選) 只把「下一個頂層、且像 `key:` 的行」當終點,減少被註解行提早截斷的狀況: ```bash sed -n '/^crds:/,/^[^ :][^:]*:/p' values.yaml # 解讀:行首第一個字不是空白或冒號,後面直到冒號 —— 大致符合「頂層 key:」的樣子 ``` 需要的話我也可以幫你把這段包成更穩健的 `awk`/`sed` 版本,或直接抓 `enabled:` 的值。 <br> --- ### 中間 `/,/` 是什麼意思? - ### gpt-5 (2025/10/02) 逗號 `,` 在 `sed` 的位址中是 **「範圍運算子」**(address range operator)。 **語法**: ``` addr1,addr2{ command } ``` 意思是:**從第一個符合 `addr1` 的那一行開始,到下一個符合 `addr2` 的那一行為止(含兩端)**,對這些行執行後面的指令。 套到你的例子: ```bash sed -n '/^crds:/,/^[^ ]/p' values.yaml ``` * `'^crds:'` 是起點位址(第一個匹配行)。 * `'^[^ ]'` 是終點位址(下一個「行首不是空白」的行)。 * `p` 只在這個範圍內列印(因為 `-n` 關掉了預設輸出)。 補充: * `addr1,addr2` 的 `addr` 可以是**行號**(如 `5`、`$`)或**正則**(如 `/^foo/`)。 * 若沒遇到 `addr2`,範圍會一路到檔尾。 * 一個檔案中可能出現多段符合的範圍;每次遇到新的 `addr1` 就會再開啟一次範圍。 * 不要把 `,` 跟 `;` 搞混:`;` 是**分隔多個指令**,`,` 是**指定位址範圍**。 <br> --- ### `,` 前後為何要用 `/` ? - ### gpt-5 (2025/10/02) 因為在 `sed` 的「位址(address)」語法裡,只要位址用的是**正規表示式**,就必須用**分隔符**把它框起來;預設分隔符就是 `/`。 所以 `'/^crds:/'` 與 `'/^[^ ]/'` 分別表示兩個「用正則表達」的位址;中間的逗號 `,` 則把這兩個位址組成**範圍**。 重點整理: * `addr` 可以是**行號**或**正則**: * 行號:`1,10p`(從第 1 行到第 10 行) * 正則:`/^foo/,/bar$/p` * 也可混用:`1,/^foo/p`、`/foo/,$p` * 當 `addr` 是**正則**時,要寫成 `/regex/`;兩側的 `/` 只是**定界符**,不屬於圖樣本身。 * 圖樣內若需要 `/`,要跳脫:`/path\/to\/file/` * 只有像 `s///` 這類指令的分隔符可以改成別的符號(如 `s,old,new,`);**位址的分隔符在 POSIX sed 中固定是 `/`**,不能任意換。 套回你的例子: ```bash sed -n '/^crds:/,/^[^ ]/p' values.yaml # 起點位址:/^crds:/ (用 /.../ 包住的正則) # 終點位址:/^[^ ]/ (用 /.../ 包住的正則) # 逗號 , 建立從「起點」到「終點」的範圍 # p 只列印這個範圍內的行(因為 -n 關閉了預設輸出) ``` <br> --- ### p 的全名是? - ### gpt-5 (2025/10/02) `p` 的全名是 **print**(列印)。更精確地說是「**print the pattern space**」——把當前的模式空間(通常就是目前這一行)輸出到標準輸出。 補充小重點: * 常搭配 `-n` 使用:`sed -n '...p'` 只列印被選中的行;沒加 `-n` 會變成重複列印。 * 有個親戚指令 `P`:只列印模式空間中**第一個換行符之前**的部分(較進階、配合 `N` 多行時才有差)。 <br> --- ## [案例] `sed -n '1,120p'` - ### gpt-5 (2025/10/22) 下面這行是 **GNU sed** 的用法,用來「只輸出第 1 到第 120 行」: ``` sed -n '1,120p' ``` ### 逐段解釋 - `sed`:stream editor,逐行讀取文字資料並依腳本處理。 - `-n`:**不要自動輸出**每一行(預設 sed 會把每行都印出);加了 `-n` 之後,只會輸出你明確要求的行。 - `-n, --quiet, --silent` suppress automatic printing of pattern space - `'1,120p'`:位址範圍 `1,120`(第 1 行到第 120 行),動作 `p`(print,印出)。 ### 怎麼用 - 讀檔案的前 120 行: ``` sed -n '1,120p' file.txt ``` - 搭配管線(例如只看 dmesg 前 120 行): ``` dmesg | sed -n '1,120p' ``` - 若直接打 `sed -n '1,120p'` 沒給檔案,sed 會等你從 **標準輸入**(鍵盤或管線)提供內容。 ### 效能小技巧 - 要更快在第 120 行就結束處理,可用: ``` sed -n '1,120p;120q' file.txt ``` `120q` 表示處理到第 120 行後立刻離開。 ### 跟其他指令的對照 - 幾乎等同: ``` head -n 120 file.txt ``` - awk 寫法: ``` awk 'NR<=120' file.txt ``` ### 常見變化 - 輸出第 121 行到檔尾: ``` sed -n '121,$p' file.txt ``` - 只看第 50 行: ``` sed -n '50p' file.txt ``` ### 注意 - 給多個檔案時,sed 會把它們當作同一個串流依序處理,行號會**接續**計算。檔案不足 120 行就輸出到檔尾為止,不會報錯。 <br> --- --- <br> ## 討論 ### sed 指令常見用法對照表 - ### gpt-4o (2025/05/12) | 語法/參數 | 說明 | 範例 | |---------|------|-----| | `sed 's/原字串/新字串/'` | 只替換第一個出現的字串 | `echo foo | sed 's/foo/bar/'` → `bar` | | `sed 's/原/新/g'` | 替換**所有**出現的原字串(加上 `g` = global) | `a a a` → `b b b` | | `sed -E 's/…/…/'` | 啟用 **延伸正規表示式(ERE)**,允許用 `+`、`()`、`?` 等語法 | `sed -E 's/(a)+/X/'` | | `sed -n 'Np'` | 顯示第 N 行(配合 `-n` 靜音模式使用) | `sed -n '2p' file.txt` | | `sed -e` | 加入多條指令(可以連續使用多個 `-e`) | `sed -e 's/a/b/' -e 's/b/c/'` | | `sed 'Nd'` | 刪除第 N 行 | `sed '2d' file.txt` | | `sed '/pattern/d'` | 刪除所有符合條件的行 | `sed '/^#/d'` → 刪除註解行(以 `#` 開頭) | | `sed 's/.../.../I'` | 不分大小寫的替換(加上 `I`) | `sed 's/foo/bar/I'` | | `sed -i` | 直接修改原檔案(**小心使用!**) | `sed -i 's/foo/bar/' file.txt` | | `sed -n '/addr1/,/addr2/p'` | **列印位址範圍**:從**首次匹配** `addr1` 到**下一次匹配** `addr2`(**含端點**)。`addr` 可為行號或 `/regex/` | `sed -n '/^BEGIN/,/^END/p' file`;`sed -n '/^crds:/,/^[^ ]/p' values.yaml` | | `sed 'A,Bd'` | **刪除位址範圍**:刪除第 `A`~`B` 行(行號範圍) | `sed '10,20d' file.txt` | | `sed -n '1,/regex/p'` | 從檔頭到**首次**匹配 `regex` 的範圍列印(含匹配行) | `sed -n '1,/^## 設定/p' README.md` | | `sed -n '/regex/,$p'` | 從**首次**匹配 `regex` 到檔尾(`$`)的範圍列印 | `sed -n '/^crds:/,$p' values.yaml` | | `sed '/addr1/,/addr2/s/舊/新/g'` | **僅在範圍內**做替換 | `sed '/^crds:/,/^[^ ]/s/false/true/' values.yaml` | | `sed -n '/addr1/,/addr2/!p'` | **印出範圍外**的行(`!` 取反;常配 `-n`) | `sed -n '/^# Start/,/^# End/!p' file` | --- ### 常見修飾符說明 | 修飾符 | 說明 | |--------|------------------------------| | `g` | 全部匹配並替換(global) | | `I` | 不區分大小寫(Ignore case) | | `p` | 印出匹配行(print) | | `d` | 刪除行(delete) | | `-n` | 靜音模式,不自動印出輸出 | --- ### sed 常見用法對照表 | 功能 | 指令範例 | 說明與結果 | |---------------------------|----------------------------------------------------------|---------------------------------------------| | 替換第一個出現的文字 | `echo 'a b c' \| sed 's/b/x/'` | 將第一個 `b` 替換成 `x` → `a x c` | | 替換所有出現的文字 | `echo 'b b b' \| sed 's/b/x/g'` | 替換所有 `b` → `x x x` | | 不分大小寫替換 | `echo 'FOO foo Foo' \| sed 's/foo/bar/Ig'` | → `bar bar bar` | | 用正規表示式取代 | `echo 'abc123' \| sed -E 's/[0-9]+/XXX/'` | 數字換成 XXX → `abcXXX` | | 只印出指定行(第2行) | `sed -n '2p' filename.txt` | 顯示第2行內容 | | 刪除指定行(第2行) | `sed '2d' filename.txt` | 移除第2行 | | 刪除符合條件的行 | `sed '/^#/d' filename.txt` | 刪除以 `#` 開頭的註解行 | | 多條替換指令連用 | `echo 'abc' \| sed -e 's/a/x/' -e 's/b/y/'` | → `xyc` | | 刪除開頭與結尾的破折號 | `echo '-hello-world-' \| sed -E 's/^-+|-+$//g'` | → `hello-world` | | 將空格換成破折號 | `echo 'gpu name v100' \| sed -E 's/[[:space:]]+/-/g'` | → `gpu-name-v100` | | 移除所有非英數字 | `echo 'GTX@1080#Ti' \| sed -E 's/[^a-zA-Z0-9]+//g'` | → `GTX1080Ti` | | 取出字串前綴(冒號前) | `echo 'key:value' \| sed -E 's/:.*//'` | → `key` | | 取出字串後綴(冒號後) | `echo 'key:value' \| sed -E 's/.*://'` | → `value` | | 將換行變空白(單行化) | `cat file.txt \| tr '\n' ' '` | 單行呈現整份文字 | | 直接修改原始檔案內容 | `sed -i 's/old/new/g' file.txt` | 替換 `old` 為 `new`(**小心備份!**) | --- ### 範例:合法 Kubernetes Label 值格式轉換(搭配 tr) ```bash echo "NVIDIA GeForce GTX 1080 Ti" \ | tr '[:upper:]' '[:lower:]' \ | sed -E 's/[^a-z0-9]+/-/g' \ | sed -E 's/^-+|-+$//g' ``` 輸出結果: ``` nvidia-geforce-gtx-1080-ti ``` <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}