grep、sed、awk:Linuxターミナルでのテキスト処理の例
grep はパターンに一致する行を探し(grep -rn error /var/log)、sed はストリームやファイルの文字列を置換します(sed -i 's/old/new/g' file)。awk は列を扱って集計します(awk '{print $1}' access.log)。パイプラインでつなぐと、3つを組み合わせて簡単なスクリプトの代わりになります。
grep、sed、awk は Linux でテキストを処理する定番の3つのツールです。grep はパターンに一致する行を探し、sed はエディタを開かずにストリーム中の文字列を置換・削除します。awk は行を列に分けて扱い、集計もできます。以下では、ログや設定ファイル、コマンドの出力を調べるときに実際に役立つ grep sed awk の例を、よくあるミスとあわせて紹介します。
どの場面でどのツールを使うか
3つのツールはいずれもテキストを1行ずつ読み、結果を標準出力に書きます。そのため | でつなぐのが簡単です。違いは、それぞれが得意とする処理にあります。
| 目的 | ツール | 例 |
|---|---|---|
| 文字列を含む行を探す | grep | grep ERROR app.log |
| 文字列を置換する | sed | sed 's/http:/https:/g' file |
| 行番号で行を絞り込む、削除する | sed | sed -n '10,20p' file |
| 列を取り出す | awk | awk '{print $1}' access.log |
| 合計、平均、グループ別の集計 | awk | awk '{s+=$10} END {print s}' |
簡単な目安は次のとおりです。行を絞り込むだけなら grep、文字列を書き換えるなら sed、列や計算が必要なら awk を使います。
grep:パターンで行を探す
grep の基本的な例
# ファイル内に error という語を含む行
grep error /var/log/syslog
# 大文字小文字を区別しない:Error, ERROR, error
grep -i error app.log
# 行番号を付けて表示
grep -n "Connection refused" app.log
# ディレクトリを再帰的に検索し、ファイル名も表示
grep -rn "DB_HOST" /etc/myapp/
# 一致したファイル名だけを表示
grep -rl "TODO" src/
# 反転:コメントと空行「以外」をすべて表示
grep -v -e '^#' -e '^$' /etc/ssh/sshd_config
# 一致した行数を数える
grep -c " 500 " access.log
-w オプションは単語単位で検索します。grep -w log は login には一致しません。-F オプションは正規表現を無効にして文字列をそのまま検索します。パターンにドット、括弧、アスタリスクが含まれる場合に便利です。
一致した行の前後の文脈を表示する
ログのエラーは1行だけ見ても原因が分からないことが多く、それを引き起こしたスタックトレースやリクエストが必要になります。そのためのオプションが文脈表示です。
# 一致の後ろ3行(After)
grep -A 3 "Traceback" app.log
# 一致の前2行(Before)
grep -B 2 "OOM" /var/log/kern.log
# 前後それぞれ5行(Context)
grep -C 5 "segfault" /var/log/syslog
grep は一致したまとまりの間に -- の行を入れるので、出力を目で追いやすくなります。
grep と正規表現
grep は既定では基本正規表現(BRE)を扱います。-E オプションを付けると拡張正規表現(ERE)になり、+、?、|、括弧をバックスラッシュなしで使えます。
| 記法 | 意味 | 例 |
|---|---|---|
^ | 行頭 | grep '^root' /etc/passwd |
$ | 行末 | grep 'bash$' /etc/passwd |
. | 任意の1文字 | grep 'h.t' |
* | 0回以上の繰り返し | grep 'ab*c' |
+(ERE) | 1回以上の繰り返し | grep -E '[0-9]+' |
|(ERE) | または | grep -E 'error|warn' |
[...] | 指定した集合の中の1文字 | grep '[Ee]rror' |
# エラーまたは警告を含む行
grep -E "ERROR|WARN" app.log
# IP アドレスだけを1行ずつ表示(-o は一致部分だけを出力)
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log
grep -P:既知の文字列に挟まれた値を取り出す
GNU grep には Perl 風の正規表現を使う -P オプションがあります。\K(ここまでの一致を捨てる)と先読みを使うと、値だけを取り出せます。
$ echo 'user_id=4821 status=ok' | grep -oP 'user_id=\K[0-9]+'
4821
$ grep -oP '"version":\s*"\K[^"]+' package.json
2.4.1
-P はすべての grep で使えるわけではありません(例えば macOS の BSD grep にはありません)。移植性が必要なスクリプトでは sed や awk を使ってください。
スクリプトの条件での grep -q
grep -q は何も表示せず、終了コードだけを返します。見つかれば 0、見つからなければ 1 です。そのまま if の条件に書けます。
if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
echo "root でのログインは無効です"
fi
結果を変数に保存して空かどうか調べるより短く書けます。また、grep は最初に一致した時点で終了します。
sed:文字列の置換と削除
置換:s コマンド
sed の基本は s/パターン/置換後/フラグ です。フラグ g を付けないと、各行で最初の一致しか置換されません。
# 置換後の結果を表示(ファイルは変更しない)
sed 's/foo/bar/' file.txt
# 行内のすべての一致を置換
sed 's/foo/bar/g' file.txt
# 大文字小文字を区別しない(GNU sed)
sed 's/error/ERROR/gI' app.log
# スラッシュを含む場合は区切り文字を変える
sed 's|/var/www|/srv/www|g' nginx.conf
sed -i:ファイルを直接書き換える
-i オプションは結果をファイルに書き戻します。元のファイルを失わないように、バックアップの接尾辞を指定しておくと安心です。
# config.ini.bak を作成してから config.ini を変更
sed -i.bak 's/^debug=true/debug=false/' config.ini
# ディレクトリ内のすべての .conf を置換
find /etc/myapp -name "*.conf" -exec sed -i 's/old-host/new-host/g' {} +
大量に書き換える前には、必ず -i を付けずに実行して出力を確認してください。macOS(BSD sed)では -i に引数が必須です。例:sed -i '' 's/a/b/' file。
行番号やパターンで行を指定する
# 10〜20行目を表示(-n は自動出力を無効にし、p は出力)
sed -n '10,20p' file.txt
# 5行目を削除
sed '5d' file.txt
# 最後の行を削除
sed '$d' file.txt
# 空行とコメント行を削除
sed -e '/^$/d' -e '/^#/d' nginx.conf
# 「listen」を含む行だけで 80 を 8080 に置換
sed '/listen/s/80/8080/' nginx.conf
# パターンに一致した行の後ろに追加(GNU sed)
sed '/^\[mysqld\]/a max_connections = 500' my.cnf
後方参照と &
パターン中の括弧で囲んだ部分は、置換側で \1、\2 として参照できます。記号 & は一致した文字列全体を表します。
# 姓と名を入れ替える(Yamada Taro → Taro Yamada)(-E で拡張正規表現)
$ echo "Yamada Taro" | sed -E 's/(\S+) (\S+)/\2 \1/'
Taro Yamada
# すべての数字を角括弧で囲む
$ echo "port 8080, timeout 30" | sed -E 's/[0-9]+/[&]/g'
port [8080], timeout [30]
# 日付 2026-10-14 を 14.10.2026 に変換
$ echo 2026-10-14 | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
14.10.2026
awk:列、条件、集計
必要な列を出力する
awk は各行をスペースとタブで区切って欄に分けます。$1 が1番目の欄、$2 が2番目、$NF が最後の欄、$0 が行全体です。
# nginx のログから IP アドレス(1列目)を取り出す
awk '{print $1}' /var/log/nginx/access.log
# /etc/passwd からユーザー名とシェルを取り出す(区切りはコロン)
awk -F: '{print $1, $7}' /etc/passwd
# 最後の列
df -h | awk '{print $NF}'
# 出力時の区切り文字を指定
awk -F: -v OFS=';' '{print $1, $3}' /etc/passwd
条件:必要な行だけを処理する
波括弧の前に条件を書くと、その条件に合う行だけに処理が適用されます。
# UID が 1000 以上のユーザー(システムユーザーを除く)
awk -F: '$3 >= 1000 {print $1}' /etc/passwd
# ステータスコードが 500 以上のリクエスト(combined 形式の9番目の欄)
awk '$9 >= 500' access.log
# 6番目の列が正規表現に一致する行
awk '$6 ~ /POST/' access.log
# ls -l の結果から 100 MB を超えるファイルを表示
ls -l | awk '$5 > 100*1024*1024 {print $9}'
BEGIN、END、NR:小さなプログラムとしての awk
BEGIN ブロックは最初の行を読む前に、END ブロックは最後の行を読んだ後に実行されます。NR は現在の行番号を保持し、END ブロックでは行の総数になります。
# 応答サイズ(10番目の欄)の合計をメガバイトで表示
awk '{sum += $10} END {printf "%.1f MB\n", sum/1024/1024}' access.log
# 最後の列(応答時間)の平均
awk '{s += $NF} END {if (NR) print s/NR}' timings.log
# CSV のヘッダー行を飛ばす
awk -F, 'NR > 1 {print $2}' users.csv
# IP ごとのリクエスト数:連想配列を使う
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head
最後の例はログ解析の定番です。キーを IP とする配列 count が sort | uniq -c の組み合わせを置き換え、IP ごとの転送量の合計などにも簡単に拡張できます。
パイプライン:grep、sed、awk の組み合わせ
3つを組み合わせると真価を発揮します。各ツールが役割を分担し、| が結果を次に渡します。
5xx エラーを多く出している IP のトップ10
grep ' 50[0-9] ' /var/log/nginx/access.log \
| awk '{print $1}' \
| sort | uniq -c | sort -rn | head -10
412 203.0.113.45
118 198.51.100.7
36 192.0.2.19
ここでは grep が大まかに行を絞り込み、awk が欄を取り出し、sort | uniq -c が重複を数えます。同じ処理を awk 1つで書くこともできます:awk '$9 ~ /^5/ {c[$1]++} END {for (i in c) print c[i], i}' access.log | sort -rn | head。こちらは応答コードの欄を直接調べるので、より正確です。
よく開かれている URL
awk '{print $7}' access.log | sed 's/?.*//' | sort | uniq -c | sort -rn | head
sed はクエリ文字列を取り除きます。これにより /search?q=a と /search?q=b が同じページとして数えられます。
コメントを除いた設定値
grep -v '^\s*#' /etc/myapp/app.env | grep '=' | awk -F= '{print $1}'
特定の1時間のエラー
grep '14/Oct/2026:03:' access.log | awk '$9 >= 500 {print $4, $7, $9}'
よくあるミス
- awk のプログラムを二重引用符で囲む。
awk "{print $1}"では、awk が起動する前にシェルが自分の変数$1を展開してしまいます。awk のプログラムや sed の式は一重引用符で囲んでください。 - sed で
gを忘れる。これを付けないと、各行の最初の一致しか置換されません。 - 確認せずに
sed -iを使う。不適切な正規表現はファイルを静かに壊します。まず-iなしで実行するか、-i.bakを使ってください。 - grep で
+や|が効かない。これは拡張構文なのでgrep -E(またはsed -E)が必要です。 - パターン中のドット。
grep 192.168.1.1は192x168y1z1にも一致します。ドットは「任意の1文字」だからです。grep -Fを使うか、\.でエスケープしてください。 - 列にスペースが含まれる。欄にスペースが入りうる場合(CSV やパス)は、区切り文字を明示してください。例:
awk -F,やawk -F'\t'。 - 無駄な cat。
cat file | grep xも動きますが、grep x fileの方が短く、余分なプロセスも生成しません。
grep、sed、awk の代わりになるツール
JSON をこれらのツールで扱うのは大変です。構造は jq(例:jq '.items[] | select(.status=="active") | .name')で、YAML は yq で扱うほうが確実です。大規模なプロジェクトを素早く再帰検索するなら、既定で .gitignore を尊重する ripgrep(コマンド rg)がよく使われます。Perl の構文に慣れているなら、perl -pe 's/a/b/g' と perl -lane 'print $F[0]' が1行で sed と awk の代わりになります。ただし grep、sed、awk はほぼすべてのサーバーに最初から入っており、インストールも不要です。だからこそ最初に覚えておく価値があります。
まとめ
grep は行を探し、sed はそれを書き換え、awk は列を扱って集計します。基本セットとして覚えておきたいのは、grep -rniE と -A/-B/-C、sed 's/.../.../g' と -i.bak、-n 'Np'、awk -F と $1、$NF、条件、END ブロックです。このような部品を組み合わせれば、ほとんどのログ解析が書けます。ここで紹介した grep sed awk の例は、find、jq、curl とあわせて、Linux コースのテキスト処理の講義で実際に試せます。
よくある質問
grep と egrep の違いは何ですか?
egrep は grep -E(拡張正規表現)の古い別名です。現在の GNU grep では egrep を呼ぶと警告が出るので、grep -E と書いてください。
ディレクトリ内のすべてのファイルから文字列を探すには?
grep -rn "文字列" /パス を使います。-r は再帰的に検索し、-n は行番号を表示します。ファイルの種類を絞るには --include="*.php" を追加します。
多数のファイルの文字列をまとめて置換するには?
検索と sed を組み合わせます:grep -rl "old" . | xargs sed -i 's/old/new/g'。実行前にコミットやバックアップを取っておくと安心です。
awk で複数の列をタブ区切りで出力するには?
出力区切り文字を指定します:awk -v OFS='\t' '{print $1, $3}' file。OFS を指定しないと、カンマで区切った欄はスペースで区切られます。
sed で行末の空白を削除するには?
sed -i 's/[[:space:]]*$//' file を実行すると、ファイル内のすべての行の末尾にあるスペースとタブが削除されます。