sedは行単位のテキスト変換(置換・削除・挿入)、awkは列単位のデータ抽出・計算・集計を得意とするUNIXの定番コマンドだ。パイプラインで組み合わせれば、設定ファイルの一括書き換えからCSV集計・ログ解析まで、あらゆるテキスト加工をワンライナーで処理できる。
「設定ファイルの値を一括で書き換えたい」「CSV から必要な列だけ抜き出して合計を出したい」「ログから特定パターンの行を除去したい」
こういった テキスト加工 の場面で登場するのが sed と awk だ。どちらもパイプラインで活躍するフィルタコマンドだが、得意分野がはっきり違う。
この記事では sed と awk の基本から実践パターン、さらに両者を組み合わせたワークフローまで解説する。
sedとawk:何が違うのか
一言で整理するとこうなる。
| sed | awk | |
|---|---|---|
| 正式名称 | Stream Editor | パターンスキャンと処理言語 |
| 得意なこと | 行単位のテキスト変換(置換・削除・挿入) | 列指向のデータ処理(抽出・計算・集計) |
| 考え方 | 「テキストを書き換える」 | 「テキストからデータを取り出す」 |
| 典型的な用途 | 設定ファイルの値変更、ログの整形 | CSV/TSV の集計、アクセスログ解析 |
どちらもパイプラインの中で | を挟んで使うフィルタコマンドという点では同じだ。sed は 変換 に強く、awk は 抽出と計算 に強い。この使い分けを頭に入れておくと、どちらを使うべきか迷わなくなる。
sed の基本操作
sed は「ストリームエディタ」の名のとおり、テキストを1行ずつ読んで変換し、結果を出力するコマンドだ。
置換
最も使う操作が s(substitute)コマンドによる置換だ。
# 各行で最初にマッチしたものだけ置換
sed 's/old/new/' file.txt
# 各行のすべてのマッチを置換(g フラグ)
sed 's/old/new/g' file.txt
# ファイルを直接編集(-i オプション)
sed -i 's/old/new/g' file.txt
行指定と範囲指定
# 3行目だけ置換
sed '3s/old/new/' file.txt
# 2行目から5行目を置換
sed '2,5s/old/new/g' file.txt
# 最終行だけ置換
sed '$s/old/new/' file.txt
行の削除・挿入・出力
# パターンにマッチする行を削除
sed '/pattern/d' file.txt
# パターンにマッチした行の後に挿入
sed '/pattern/a\new line' file.txt
# パターンにマッチした行の前に挿入
sed '/pattern/i\new line' file.txt
# 特定範囲の行だけ出力(-n と p の組み合わせ)
sed -n '10,20p' file.txt
複数操作と区切り文字
# 複数の置換を一度に実行
sed -e 's/foo/bar/g' -e 's/baz/qux/g' file.txt
# 区切り文字を変更(パスやURLの操作で便利)
sed 's|/usr/local|/opt|g' config.txt
区切り文字は / 以外にも | # @ などが使える。パスを扱うときに / をエスケープしなくて済むので覚えておくと便利だ。
sed 実践パターン
設定ファイルの一括書き換え
サーバー移行やデプロイ時に設定値を一括で変更するケースだ。
# .env ファイルの DB_HOST を書き換え
NEW_HOST="db-prod.example.com"
sed -i "s/DB_HOST=.*/DB_HOST=${NEW_HOST}/" .env
# 特定行をコメントアウト
sed -i 's/^PermitRootLogin yes/# PermitRootLogin yes/' /etc/ssh/sshd_config
# コメントを解除
sed -i 's/^# *PermitRootLogin/PermitRootLogin/' /etc/ssh/sshd_config
ログファイルのクリーニング
# 空行を除去
sed '/^$/d' file.txt
# 先頭と末尾の空白を除去
sed 's/^[[:space:]]*//;s/[[:space:]]*$//' file.txt
# ANSIエスケープシーケンス(色コード)を除去
sed 's/\x1b\[[0-9;]*m//g' colored-output.txt
バッチ処理(find との組み合わせ)
# プロジェクト内の全 .txt ファイルで年号を一括置換
find . -name "*.txt" -exec sed -i 's/2025/2026/g' {} +
# .env.example から .env を生成しつつ値を置換
sed 's/DB_PASSWORD=changeme/DB_PASSWORD=s3cur3P@ss/' .env.example > .env
awk の基本操作
awk はテキストを フィールド(列)単位で処理する言語だ。各行を自動的に空白で分割し、$1、$2 などでアクセスできる。
なお、gawk 5.4.0 ではデフォルトの正規表現エンジンが MinRX に変更された。完全 POSIX 準拠になったため、従来の GNU 拡張に依存していたパターンは動作が変わる可能性がある。従来のエンジンを使いたい場合は環境変数 GAWK_GNU_MATCHERS=1 を設定する。
列の抽出
# 1列目と3列目を出力
awk '{print $1, $3}' data.tsv
# 区切り文字を指定(CSV の場合)
awk -F',' '{print $1, $2}' data.csv
# 行全体を出力($0 は行全体)
awk '{print $0}' file.txt
パターンマッチと条件
# "error" を含む行だけ出力
awk '/error/ {print $0}' logfile.txt
# 3列目が100より大きい行を出力
awk '$3 > 100 {print $1, $3}' data.txt
# 複数条件の組み合わせ
awk '$3 > 100 && $2 == "active" {print $1, $3}' data.txt
組み込み変数
awk には便利な組み込み変数がある。
| 変数 | 意味 |
|---|---|
NR | 現在の行番号(Number of Records) |
NF | 現在の行のフィールド数(Number of Fields) |
FS | フィールド区切り文字(Field Separator) |
OFS | 出力時のフィールド区切り文字 |
# 行番号付きで出力
awk '{print NR": "$0}' file.txt
# 各行のフィールド数を表示
awk '{print NR": "NF" fields"}' data.txt
# 最終フィールドを出力
awk '{print $NF}' data.txt
BEGIN / END ブロックと集計
# ヘッダーとフッターを付ける
awk 'BEGIN {print "Name,Score"} {print $1","$3} END {print "---done---"}' data.txt
# 2列目を合計
awk '{sum += $2} END {print "Total:", sum}' sales.txt
# 平均を計算
awk '{sum += $2; count++} END {print "Average:", sum/count}' sales.txt
printf による出力整形
# 左寄せ20文字、右寄せ10文字(小数点2桁)
awk '{printf "%-20s %10.2f\n", $1, $3}' data.txt
printf は C 言語と同じフォーマット指定子が使える。テーブル形式の出力を作るときに重宝する。
awk 実践パターン
CSV/TSV データの集計
# カテゴリ別の売上合計(連想配列を使用)
# 入力: category,product,amount の CSV
awk -F',' '{
sales[$1] += $3
}
END {
for (cat in sales)
printf "%-15s %10.0f\n", cat, sales[cat]
}' sales.csv
# 最大値・最小値・平均を一度に計算
awk 'BEGIN {max = -999999; min = 999999}
{
sum += $2
count++
if ($2 > max) max = $2
if ($2 < min) min = $2
}
END {
printf "Max: %.2f Min: %.2f Avg: %.2f\n", max, min, sum/count
}' data.txt
アクセスログ解析
# IPアドレス別のアクセス数(上位10件)
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head -10
# HTTPステータスコード別の集計
# CLF形式: IP - - [date] "method path proto" status size
awk '{print $9}' access.log | sort | uniq -c | sort -rn
出力フォーマット整形
# テーブル形式でユーザー情報を表示
awk -F':' 'BEGIN {
printf "%-20s %-6s %-6s %s\n", "USER", "UID", "GID", "HOME"
printf "%-20s %-6s %-6s %s\n", "----", "---", "---", "----"
}
$3 >= 1000 && $3 < 65534 {
printf "%-20s %-6s %-6s %s\n", $1, $3, $4, $6
}' /etc/passwd
sed + awk の組み合わせ
sed と awk はパイプラインで繋ぐことで真価を発揮する。sed で前処理(整形・フィルタ)→ awk で集計という流れだ。
# CSV のヘッダー行を除去してから3列目を集計
sed '1d' sales.csv | awk -F',' '{sum += $3} END {print "Total:", sum}'
# ログから ERROR 行を抽出し、時刻とメッセージだけ表示
sed -n '/ERROR/p' app.log | awk '{print $1, $2, substr($0, index($0,$5))}'
# /etc/passwd からシェルが bash のユーザーだけ抽出し整形
grep '/bash$' /etc/passwd | awk -F':' '{printf "%-15s UID=%-6s %s\n", $1, $3, $6}'
# syslog から今日のエラーを集計
sed -n "/$(date '+%b %e')/p" /var/log/syslog | awk '/error|fail/ {count[$5]++} END {for (s in count) print count[s], s}' | sort -rn
モダン代替:sd
sd は Rust 製の sed 代替ツールだ。sed の s/old/new/g という構文に比べて、エスケープが少なくシンプルに書ける。
インストール
# WSL 環境なら Linux と同じ
cargo install sd
基本的な使い方
# 標準入力から置換
echo "hello world" | sd 'world' 'earth'
# ファイルを直接編集
sd 'old' 'new' file.txt
sed との比較
| 操作 | sed | sd |
|---|---|---|
| 基本置換 | sed 's/foo/bar/g' | sd 'foo' 'bar' |
| パス置換 | sed 's|/usr/local|/opt|g' | sd '/usr/local' '/opt' |
| 正規表現グループ | sed 's/\(foo\)/[\1]/g' | sd '(foo)' '[$1]' |
| ファイル編集 | sed -i 's/foo/bar/g' file | sd 'foo' 'bar' file |
sd は正規表現にデフォルトで PCRE 風の構文を使うため、\( \) のようなエスケープが不要だ。パスの置換でも区切り文字を変更する必要がない。
# 複数行マッチ(v1.1.0 以降)
sd --across 'start\n.*\nend' 'replaced' file.txt
sed に慣れているなら無理に乗り換える必要はない。ただ、正規表現のエスケープが複雑になりがちなワンライナーでは sd のほうがミスが減る。使い分けの目安としては、シンプルな置換は sd、行指定や複雑なスクリプトは sed というのがバランスが良い。
応用テクニック
awk の連想配列でグルーピング集計
awk の連想配列はデータのグルーピングに強力だ。SQLの GROUP BY に相当する操作がワンライナーで書ける。
# ステータスコード別のレスポンスサイズ合計
awk '{status[$9]++; size[$9]+=$10} END {
for (s in status)
printf "%s: %d requests, %.2f MB\n", s, status[s], size[s]/1024/1024
}' access.log
sed でファイルの特定ブロックを抽出する
2つのパターンに挟まれた範囲を抽出するには、範囲アドレスを使う。
# BEGIN と END に挟まれたブロックを抽出
sed -n '/^BEGIN$/,/^END$/p' config.txt
# 特定の関数定義を抽出(中括弧の対応は保証しない簡易版)
sed -n '/^function setup/,/^}/p' script.sh
awk でフィールド区切りの変換
入力と出力で異なる区切り文字を使う場面は多い。OFS(Output Field Separator)で制御する。
# TSV → CSV 変換
awk 'BEGIN {FS="\t"; OFS=","} {$1=$1; print}' data.tsv > data.csv
# CSV → パイプ区切り
awk -F',' 'BEGIN {OFS="|"} {$1=$1; print}' data.csv
$1=$1 は awk にレコードを再構築させるトリックだ。これがないと OFS が適用されない。
sed の保持スペース(ホールドスペース)
sed には「パターンスペース」(現在の行)と「保持スペース」(一時バッファ)の2つのバッファがある。h/H(保存)、g/G(復元)、x(交換)で操作する。
# 空行の直前の行を出力(セクション末尾の行を取得)
sed -n '/^$/!{h;d}; /^$/{x;p}' file.txt
高度なテクニックだが、複雑なテキスト変換では保持スペースが唯一の解になることがある。
FAQ
sedとawkどちらを使うべき?
行単位の書き換え(置換・削除・挿入)なら sed。列の抽出や計算・集計なら awk。迷ったら「変換したいか、データを取り出したいか」で判断すればいい。
macOSのsedとLinuxのsedの違いは?
macOS のデフォルトは BSD sed で、-i の挙動が異なる。GNU sed は sed -i 's/...' で直接編集できるが、BSD sed は sed -i '' 's/...' と空のバックアップ拡張子が必要。Homebrew で brew install gnu-sed すれば gsed として GNU 版が使える。
awkとgawkの違いは?
awk はPOSIX仕様のコマンド。gawk は GNU による拡張実装で、連想配列の for...in、printf の拡張、BEGINFILE/ENDFILE ブロックなど追加機能がある。Linux では awk は通常 gawk へのシンボリックリンクだ。
sedでマルチバイト文字(日本語)は正しく扱える?
GNU sed はロケール設定に従ってマルチバイト文字を処理する。LC_ALL=ja_JP.UTF-8 が設定されていれば日本語の置換も正しく動作する。.(任意の1文字)も1バイトではなく1文字にマッチする。
awkで大きなファイル(数GB)を処理できる?
awkは行単位でストリーム処理するため、メモリにファイル全体を読み込まない。数GBのログファイルでも問題なく処理できる。ただし連想配列に大量のキーを蓄積するとメモリを消費するので、sort | uniq -c との使い分けが必要な場面もある。
sdとsedどちらを選ぶべき?
シンプルな置換(特にパスやURLを含む場合)は sd のほうが楽。行アドレスや保持スペースを使う高度な処理は sed でないとできない。両方入れておいて使い分けるのがベストだ。
sedの-iオプションなしで安全にプレビューするには?
sed 's/old/new/g' file.txt のように -i を付けずに実行すれば、結果が標準出力に表示されるだけでファイルは変更されない。diff と組み合わせて sed 's/old/new/g' file.txt | diff file.txt - とすると差分がわかりやすい。
まとめ
sed と awk はテキスト加工における定番の組み合わせだ。
- sed — 行単位の置換・削除・挿入。設定ファイルの書き換えやログのクリーニングに最適
- awk — 列単位の抽出・計算・集計。CSV/TSV の処理やアクセスログ解析に最適
- sed + awk — パイプラインで繋いで前処理→集計。sed で整形、awk で計算が基本パターン
- sd — sed の構文が面倒なときのモダン代替。エスケープが少なくシンプル
どちらも50年近い歴史を持つ枯れたツールで、Linux サーバーなら必ずインストールされている。まずは sed の s コマンドと awk の {print $1} から始めて、徐々にパターンを増やしていくのがおすすめだ。関連ツールの詳細は grep・ripgrep完全ガイド、jq完全ガイド、Rust製CLIツール特集、CLIツール完全マップ を参照してほしい。