Linux -  sedの仕組みと基本的な使い方

# テキスト処理ツール sed

## 1.1 sed の作業原理

sed は Stream Editor の略で、vi とは異なり、sed は「行編集器」です。
ファイルまたはパイプから1行を読み取り、1行を処理し、1行を出力します。次に再び1行を読み取り、処理し、出力し、最後の行までこれを繰り返します。
各行を処理する際には、現在処理中の行を一時的なバッファーである「パターンスペース」に保存し、その後sedコマンドを使ってバッファー内の内容を処理します。処理が完了すると、バッファーの内容を画面に送ります。このプロセスをファイルの最後まで繰り返します。
一行ずつ処理する設計パターンにより、sedは非常に高いパフォーマンスを実現します。大きなファイルを読み込む際に遅延を引き起こすことはありません。
vi コマンドで数十MBから数百MBのファイルを開くと、遅延が発生することがありますが、これはviがファイルを一度にメモリに読み込んでから開くためです。sed はこのような状況を避け、一行ずつ処理することで開始速度と実行速度が非常に速くなります。

### 関連文書
- [sed Sourceforge](http://sed.sourceforge.net/)
- [sed man page(Man7)](https://man7.org/linux/man-pages/man1/sed.1.html)
- [GNU sed manual](http://www.gnu.org/software/sed/manual/sed.html)

## 1.2 sed の基本的な使用方法

### フォーマット：
```shell
sed [OPTION]... {script-only-if-no-other-script} [input-file]...
```

### よく使われるオプション
- `-n|--quiet|--silent` # パターンスペースの内容を画面に表示しない、つまり自動的に出力しない
- `-e script|--expression=script` # 複数のスクリプト、またはの関係のスクリプト
- `-f script-file|--file=script-file` # 指定されたファイルから編集スクリプトを読み込む
- `-i[SUFFIX]|--in-place[=SUFFIX]` # `-i` はファイルを直接変更、`-i.bak` は元のファイルを .bak サフィックスでバックアップする
- `-c|--copy` # `-i` と一緒に使うと、元のファイルを保持する
- `-l N|--line-length=N` # 各行の長さを指定、長すぎる場合は複数行に分割する
- `--posix` # GNU拡張を無効にする
- `-E|-r|--regexp-extended` # 拡張正規表現を使用する
- `-s|--separate` # 複数のファイルを独立したファイルとして扱い、単一の連続した長いファイルストリームとしては扱わない

### サポートされている組み合わせ
- `-ir` # この組み合わせはサポートされていません
- `-ri` # サポートされています
- `-i -r` # サポートされています
- `-ni` # この組み合わせは危険で、ファイルが空になる可能性があります

### スクリプトのフォーマット：
```plaintext
'AddrCmd' # どの行で、どの操作を実行するかのアドレスコマンド
```

### アドレスのフォーマット：
- 空白の場合、全文を処理する
- 単一アドレス、特定の行を指定
    - `N` # 特定の行番号
    - `$` # 最終行
    - `/pattern/` # マッチする各行

### 範囲アドレス：
- `M,N` # 第M行から第N行まで
- `M,+N` # 第M行から第M+N行まで、例：`3,+4` は第3行から第7行まで
- `/pattern1/,/pattern2/` # 最初のマッチする行から二番目のマッチする行まで
- `M,/pattern/` # 行番号で始まり、マッチで終わる

### 指示コマンド：
- `p` # 現在のパターンスペースの内容を出力、デフォルトの出力に追加
- `Ip` # 大文字小文字を区別せずに出力
- `d` # パターンスペースのマッチした行を削除し、次のループをすぐに開始
- `a [\]text`  # 指定された行の後にテキストを追加。複数行追加を実現するために `\n` を使用することが可能
- `i [\]text`  # 行の前にテキストを挿入
- `c [\]text`  # 行を一行または複数行のテキストに置換
- `w file` # パターンマッチの行を指定されたファイルに保存
- `r file` # 指定されたファイルからテキストを読み込み、パターンスペースにマッチした行の後に追加
- `=` # パターンスペースの行番号を出力
- `!` # パターンスペースのマッチ行に対して逆の処理を行
- `q`          # sedの実行を終了または退出

### 検索と置換：
```shell
s/pattern/replace/modifier  # 検索と置換、他の区切り文字を使用することも可能：s@@@、s###
```

### 修飾子
- `g` # 行内での全体替換
- `p` # 置換成功の行を表示
- `w file` # 罢処レ成功の行をファイルに保存
- `I|i` # 大文字小文字を区別しない

### 後方参照
- `\1` # 最初の捕捉グループ
- `\2` # 第二の捕捉グループ
- `\N` # N番目のグループ
- `&` # すべての検索内容

### スクリプト例:
```shell
#例：標準入力を待機中、scriptが空、デフォルトでは直接出力されます
[root@rocky86 ~]# sed ''
123
123
[root@rocky86 ~]# sed '' -
123
123

#例：デフォルト出力を無効にする、scriptが空なら、出力は何もありません
[root@rocky86 ~]# sed '' /etc/issue
\S
Kernel \r on an \m

#例：script内でpコマンドを実行し、デフォルトの出力を加えると、すべての行が2回表示されます。
[root@rocky86 ~]# sed 'p' /etc/issue
\S
\S
Kernel \r on an \m
Kernel \r on an \m

#例：デフォルト出力を無効にし、scriptが空の場合、出力は何もありません。
[root@rocky86 ~]# sed -n '' /etc/issue
[root@rocky86 ~]# 

#例：-nオプションでデフォルト出力を無効にし、script内でpコマンドを実行
[root@rocky86 ~]# sed -n 'p' /etc/issue
\S
Kernel \r on an \m

#例：最初の行を出力
[root@rocky86 ~]# sed -n '1p' /etc/passwd
root:x:0:0:root:/root:/bin/bash

#例：最後の行を出力
[root@rocky86 ~]# sed -n '$p' /etc/passwd
mage:x:1000:1000::/home/mage:/bin/bash

#例：正規表現マッチ、rootが含まれている行を出力する
[root@rocky86 ~]# sed -n '/root/p' /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin

#例：正規表現マッチ、rootで始まる行を出力する
[root@rocky86 ~]# sed -n '/^root/p' /etc/passwd
root:x:0:0:root:/root:/bin/bash

#例：正規表現マッチ、bashで終わる行を出力する
[root@rocky86 ~]# sed -n '/bash$/p' /etc/passwd
root:x:0:0:root:/root:/bin/bash
jose:x:1010:1010::/home/jose:/bin/bash
jerry:x:1011:1011::/home/jerry:/bin/bash

#例：正規表現マッチ、コメント行の行番号を表示
[root@rocky86 0723]# sed -n '/^#/=' /etc/fstab 
2
3
4

#例：行番号が始まる、正規表現が終了
[root@rocky86 0723]# sed -n '8,/root/p' /etc/passwd
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin

#例：内容の追加
[root@rocky86 0723]# cat test.txt 
aaa
bbb
ccc
bbb

#例：指定した行後に挿入
[root@rocky86 0723]# sed '/bbb/a\---' test.txt 
aaa
bbb
---
ccc
bbb
---

#例：指定した行の前に挿入
[root@rocky86 0723]# sed '2i\---' test.txt 
aaa
---
bbb
ccc
bbb
[root@rocky86 0723]# sed '2,4i\---' test.txt 
aaa
---
bbb
---
ccc
---
bbb

#例：置換、最初の行を---に置換
[root@rocky86 0723]# sed "1c\---" test.txt 
---
bbb
ccc
bbb

#例：置換、最初の行を二行に置換
[root@rocky86 0723]# sed "1c\---\n+++" test.txt 
---
+++
bbb
ccc
bbb

#例：置換、複数行を一行に置換
[root@rocky86 0723]# sed "1,2c\---" test.txt 
---
ccc
bbb

#例：\の機能
[root@rocky86 0723]# sed '2a   *******' test.txt 
aaa
bbb
*******
ccc
bbb
[root@rocky86 0723]# sed '2a\   *******' test.txt 
aaa
bbb
   *******
ccc
bbb

#例：IP行を取得
[root@rocky86 ~]# ifconfig ens160 | head -n 2 | tail -n 1
       inet 10.0.0.158 netmask 255.255.255.0 broadcast 10.0.0.255
[root@rocky86 ~]# ifconfig ens160 | sed -n '2p'
       inet 10.0.0.158 netmask 255.255.255.0 broadcast 10.0.0.255
[root@rocky86 ~]# ifconfig ens160 | sed -n '/netmask/p'
       inet 10.0.0.158 netmask 255.255.255.0 broadcast 10.0.0.255        

#例：コマンド展開
[root@rocky86 ~]# sed -n "/$(whoami)/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
##最初の行
[root@rocky86 ~]# sed -n "$[$(id -u)+1]p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
##最後から2行目
[root@rocky86 ~]# sed -n "$(echo $[`cat /etc/passwd|wc -l`-1])p" /etc/passwd
jerry:x:1011:1011::/home/jerry:/bin/bash

#例：変数展開
[root@rocky86 ~]# sed -n "/$USER/p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@rocky86 ~]# number=1;sed -n "${number}p" /etc/passwd
root:x:0:0:root:/root:/bin/bash

#例：変数とコマンド
[root@rocky86 0723]# sed -n "$(echo $UID+1|bc)p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
[root@rocky86 0723]# sed -n "$(echo $UID+1|bc),$(echo $UID+3|bc)p" /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin

#例：＃で始まる行以外を表示
[root@centos8 ~]#sed -n '/^#/!p' fstab

#例：コメント行と空行を表示しない
[root@rocky86 0723]# sed '/^$/d;/^#/d' fstab
[root@rocky86 0724]# sed -En '/^(#|$)/!p' fstab
[root@rocky86 0723]# grep -Ev '^#|^$' fstab

#例：ファイルの変更
##変更前にバックアップ
[root@rocky86 0723]# seq 10 > 10.txt
[root@rocky86 0723]# sed -i.bak '2,7d' 10.txt
[root@rocky86 0723]# ll 10*
-rw-r--r-- 1 root root  9 Jul 23 19:02 10.txt
-rw-r--r-- 1 root root 21 Jul 23 19:01 10.txt.bak
[root@rocky86 0723]# cat 10.txt
1
8
9
10
##バックアップなし
[root@rocky86 0723]# rm -f 10.txt.bak
[root@rocky86 0723]# seq 10 > 10.txt
[root@rocky86 0723]# sed -i '2,7d' 10.txt
[root@rocky86 0723]# ll 10*
-rw-r--r-- 1 root root 9 Jul 23 19:05 10.txt
[root@rocky86 0723]# cat 10.txt
1
8
9
10

#例：コメント行と空行を削除
[root@rocky86 0723]# sed -i '/^#/d;/^$/d' fstab
[root@rocky86 0723]# sed -i '/^\(#\|$\)/d' fstab

#例：検索置換と&
[root@rocky86 0723]# sed -n 's/root/ROOT/gp' /etc/passwd
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
operator:x:11:0:operator:/ROOT:/sbin/nologin
[root@rocky86 0723]# sed -n 's/root/&er/gp' /etc/passwd
rooter:x:0:0:rooter:/rooter:/bin/bash
operator:x:11:0:operator:/rooter:/sbin/nologin
[root@rocky86 0723]# sed -n 's/r..t/&er/gp' /etc/passwd
rooter:x:0:0:rooter:/rooter:/bin/bash
operator:x:11:0:operator:/rooter:/sbin/nologin
ftp:x:14:50:FTP User:/var/fterp:/sbin/nologin
clevis:x:994:988:Clevis Decrypterion Framework unprivileged 
user:/var/cache/clevis:/sbin/nologin

#例：パーティションの使用率を取得
[root@rocky86 0723]# df | sed -En '/^\/dev\/sd/s/.* ([0-9]+)%.*/\1/p'
26
[root@rocky86 0724]# df | sed -En 's/^\/dev.* ([0-9]+)%.*/\1/p' | sort -nr
26
16
1
```