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 %}