はじめに
コマンドラインで文字列を操作するとき、sedは非常に強力な選択肢です。ファイルの内容を置換したり、特定の行を抽出したりと、正規表現と組み合わせることで複雑な処理も1行で完結します。
本記事では、sedの基本的な使い方から、正規表現を活用した応用的なテクニックまでを順を追って解説します。初学者が躓きやすいポイントにも触れながら進めていくので、コマンドに慣れていない方でも読み進められる構成になっています。
参考:sed, a stream editor - GNU公式ドキュメント
sedと正規表現で最初の1文字をHに置き換える仕組み
今回メインで扱うコマンドは以下です。
| 要素 | 内容 |
|---|---|
sed | ストリームエディタ。ファイルや標準入力を1行ずつ処理する |
's/./H/' | 置換コマンド。s/検索パターン/置換文字列/ の形式 |
. | 正規表現の任意の1文字にマッチするメタ文字 |
H | 置換後の文字列 |
input.txt | 処理対象のファイル |
フラグなしのsコマンドは、各行の最初にマッチした箇所だけを置換します。そのため、各行の先頭の1文字だけがHに変わります。
実行前の状態
まず、以下のコマンドでinput.txtを作成します。タブ文字を含む行があるため、ターミナルでタブを入力する際はCtrl+vを押した後にTabキーを押す必要があります。
cat << 'EOF' > input.txt
hello.
world.
hello world.
hello.
1hello 2world.
EOF
input.txt の内容:
hello.
world.
hello world.
hello.
1hello 2world.
実行後の状態
出力結果:
Hello.
Horld.
Hello world.
Hhello.
Hhello 2world.
空行はマッチする文字がないため変化しません。5行目はタブ文字が先頭にあるため、タブがHに置き換わります。6行目は1が最初の1文字なので1がHになります。
実行画像

GNU sedとBSD sedの挙動の違い
macOSに標準搭載されているのはBSD版のsed、LinuxにはGNU版のsedが搭載されていることが多いです。同じコマンドでも挙動が異なる場合があります。
| 項目 | GNU sed | BSD sed |
|---|---|---|
-iオプション(インプレース編集) | sed -i 's/a/b/' file | sed -i '' 's/a/b/' file(バックアップ拡張子が必要) |
\t(タブ) | 正規表現内で使用可能 | 使用できない場合がある |
\+、\? | BREで使用可能 | 使用できない |
-Eオプション | 拡張正規表現を有効化 | 同様に使用可能 |
本記事では断りのない限りBSD版のsedでコマンドを記述します。
helloをHIに全置換する
| 要素 | 内容 |
|---|---|
s | 置換コマンド |
hello | 検索する文字列 |
HI | 置換後の文字列 |
g | グローバルフラグ。行内のすべてのマッチを置換する |
フラグなしだと各行で最初の1件だけ置換されます。gをつけることで、1行に複数のマッチがあっても全件置換されます。6行目のhelloも置換対象になります。
helloを含む行だけを抽出する
| 要素 | 内容 |
|---|---|
-n | デフォルトの出力を抑制する |
/hello/ | マッチするパターン |
p | マッチした行を出力する |
sedはデフォルトで全行を出力します。-nで出力を抑制し、pコマンドで明示的にマッチした行だけを表示することで、grepのような抽出処理が実現できます。
後方参照を使った列の抽出
| 要素 | 内容 |
|---|---|
\(hello\) | グループ化。マッチした内容を\1として参照できる |
.* | 任意の文字列(0文字以上) |
\1 | 1つ目のグループに一致した文字列を参照する |
p | 置換が成功した行のみ出力 |
正規表現のグループ化と後方参照を使うことで、行の一部分だけを切り出す列の抽出が可能です。
後方参照でhelloとworldの順番を入れ替える
| 要素 | 内容 |
|---|---|
\(hello\) | 1つ目のグループ(\1で参照) |
\(world\) | 2つ目のグループ(\2で参照) |
\2 \1 | グループの順序を入れ替えて出力 |
後方参照はグループ番号の順に\1、\2と増えていきます。「hello world」とマッチする行で置換が行われ、world helloとして出力されます。
連続した改行を取り除く
| 要素 | 内容 |
|---|---|
N | 次の行をパターンスペースに読み込んで連結する |
s/^\n// | 先頭の改行文字を削除する |
sedは通常1行ずつ処理します。Nコマンドを使うことで次の行も読み込み、2行分をまとめて処理できます。空行を含む連続した改行を除去する場合、Nがないとパターンスペースに改行が含まれないためマッチしません。
スペースをアンダースコアに置き換える
| 要素 | 内容 |
|---|---|
| 半角スペース | |
_ | 置換後の文字列 |
g | 行内の全マッチを置換 |
単純な見た目ですが、全角スペースとの混同が原因でマッチしないケースがよくある失敗例です。正規表現でのスペース処理は文字の種類に注意が必要です。
タブ文字をSPACEという文字列に置き換える
| 要素 | 内容 |
|---|---|
\t | タブ文字を表すエスケープシーケンス |
SPACE | 置換後の文字列 |
BSD版のsedでは\tが使えない場合があります。その際はCtrl+v → Tabで実際のタブ文字を埋め込む方法があります。input.txtの5行目にはタブが含まれているため、このコマンドで確認できます。
ドット(.)をエスケープしてDOTに置換する
| 要素 | 内容 |
|---|---|
\. | エスケープされたドット。文字としての.にマッチ |
DOT | 置換後の文字列 |
正規表現において.は「任意の1文字」を意味するメタ文字です。文字としてのドットにマッチさせるには\.とエスケープする必要があります。エスケープを忘れると、すべての文字がマッチして意図しない置換が起こります。
数字を#に置き換える
| 要素 | 内容 |
|---|---|
[0-9] | 0から9の任意の1文字にマッチする文字クラス |
# | 置換後の文字列 |
[0-9]は正規表現の文字クラスで、数字1文字を表します。\dはBSD版のsedでは使えないため、[0-9]を使うのが確実です。
シェル変数・$1を活用した動的な置換
sedのコマンドをダブルクォートで囲むと、シェル変数を展開できます。
| 要素 | 内容 |
|---|---|
"s/hello/$var/" | ダブルクォート。シェルが$varを展開してargumentになる |
's/hello/$var/' | シングルクォート。$varがそのまま文字列として扱われる |
シングルクォートでは変数は展開されません。シェルスクリプトでは$1(第1引数)を使うことがよくあります。例えばsed "s/hello/$1/" input.txtとすれば、スクリプト実行時に渡した引数で置換文字列を変えることができます。
最長一致と最短一致の違い
| 要素 | 内容 |
|---|---|
h.*o | hで始まりoで終わる、できるだけ長い文字列にマッチ(最長一致) |
sedの.*はデフォルトで最長一致(greedy)です。hello worldに対してh.*oはhelloではなくhello woの最後のoまで含むより長い部分にマッチします。
最短一致はBSD版のsedではサポートされていません。GNU sedでも\{0,\}のような迂回策が必要です。最短一致が必要な場合はPerlやPythonの利用を検討してください。
特定の行だけを対象にした置換
| 要素 | 内容 |
|---|---|
4 | 4行目だけを処理対象にするアドレス指定 |
s/hello/HI/ | 置換コマンド |
アドレス指定を使うと、特定の行番号や正規表現にマッチする行だけに処理を限定できます。2,4s/hello/HI/のように範囲指定も可能です。
BREとEREについて
sedの正規表現にはBRE(基本正規表現)とERE(拡張正規表現)の2種類があります。
BREの例:
BREではグループ化に\(と\)を使います。
EREの例:
-Eオプションを付けるとEREが有効になり、(と)だけでグループ化できます。BREでは\(が必要な箇所も、EREでは省略できるためコマンドがシンプルになります。
拡張正規表現でhelloまたはworldをXに置換する
| 要素 | 内容 |
|---|---|
-E | 拡張正規表現を有効化 |
(hello|world) | helloまたはworldにマッチ |
X | 置換後の文字列 |
EREでは|のままで動作します。
sedでよく使う正規表現の一覧
. | 任意の1文字 | s/./X/ |
* | 直前の文字の0回以上の繰り返し | s/el*/X/ |
^ | 行頭 | s/^/> / |
$ | 行末 | s/$/ end/ |
[abc] | a、b、cのいずれか1文字 | s/[abc]/*/g |
[^abc] | a、b、c以外の1文字 | s/[^abc]//g |
[0-9] | 数字1文字 | s/[0-9]/#/g |
\(…\) | グループ化(BRE) | s/\(hello\)/[\1]/ |
\1 | 後方参照 | s/\(hello\) \(world\)/\2\1/ |
\. | 文字としてのドット | s/\./,/g |
\t | タブ文字(GNU sed) | s/\t/ /g |
+ | 1回以上(ERE) | s/[0-9]+/#/g |
? | 0または1回(ERE) | s/e?l/X/g |
| | または(ERE) | s/hello|world/X/g |
逆引き:やりたいことからコマンドを探す
例1:行頭に文字列を追加する
cat << 'EOF' > input.txt
hello.
world.
EOF
^で行頭にマッチし、> を挿入します。
例2:行末の不要なスペースを削除する
cat << 'EOF' > input.txt
hello
world
EOF
$で行末にマッチし、その手前のスペースを削除します。
例3:空行を削除する
cat << 'EOF' > input.txt
hello.
world.
EOF
^$は行頭と行末が直接続く、つまり空行にマッチします。dコマンドでその行を削除します。
うまく動かないときに見直したいコマンドの落とし穴
全角スペースの削除で何も変わらない
cat << 'EOF' > sample.txt
hello world
EOF
このコマンドは全角スペースを削除するように見えますが、ターミナルの文字コードやコピペの際に半角スペースに変換されていると、マッチせず何も変わりません。ファイルに実際に全角スペースが入っているかcat -Aなどで確認してください。
HTMLタグの除去で行全体が消える
cat << 'EOF' > sample.txt
<p>hello</p> and <span>world</span>
EOF
.*は最長一致のため、<p>から最後の</span>まで行全体がマッチして消えてしまいます。HTMLタグの除去には[^>]*を使うことで最初の>までに限定できます。
日付フォーマット変換でバックスラッシュエラーが起きる
cat << 'EOF' > sample.txt
2024-04-16
EOF
BSD版のsedでは\+(1回以上の繰り返し)がサポートされていない場合があります。この場合は[0-9][0-9]*のように書き換えるか、-Eオプションで拡張正規表現に切り替えてください。
sedと正規表現を使いこなして文字列処理の幅を広げる
sedは一見シンプルなコマンドですが、正規表現と組み合わせることで置換・抽出・整形など幅広い処理に対応できます。初学者のうちはBREとEREの違いや、BSD版とGNU版の挙動の差に戸惑うことがありますが、本記事で紹介したコマンドを実際に動かしながら確認することで理解が深まります。まずは小さなファイルで試し、少しずつ応用範囲を広げていきましょう。
