テキスト処理:grep・sed・awk 入門
Unix の設計思想のひとつに「テキストを流して処理する」というものがあります。コマンドはそれぞれ単機能に徹し、標準入出力でつないで組み合わせる――パイプ(|)を使ったワンライナーがその体現です。
今回扱う grep・sed・awk は、そのパイプラインの中心を担うテキスト処理の三本柱です。役割分担はシンプルで、grep は「検索・絞り込み」、sed は「変換・置換」、awk は「列の抽出と集計」です。サーバーログの解析や設定ファイルの一括書き換えなど、日常的な Linux 操作でこの3つを使いこなせれば、大半のテキスト処理は手の届く範囲に収まります。
grep:テキストを検索・絞り込む
grep はファイルや標準入力から、パターンに一致する行を抽出します。正規表現が使えるのが強みで、単純な文字列検索から複雑なパターンマッチまで対応します。
# 基本:ファイルから "error" を含む行を表示
grep "error" /var/log/syslog
# -i:大文字・小文字を無視
grep -i "error" /var/log/syslog
# -n:行番号を表示
grep -n "error" /var/log/syslog
# -v:パターンに一致しない行を表示(除外)
grep -v "DEBUG" app.log
# -r:ディレクトリを再帰的に検索
grep -r "TODO" ./src/
# -r と -n を組み合わせる(よく使う)
grep -rn "TODO" ./src/
拡張正規表現(-E)
-E オプションを付けると拡張正規表現が使えます。|(OR)や +(1回以上)、?(0または1回)などが利用できます。
# "error" または "warning" を含む行
grep -E "error|warning" app.log
# IPアドレスっぽい文字列を探す
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log
パイプとの組み合わせ
grep の真価はパイプで発揮されます。他のコマンドの出力を絞り込む用途が非常に多いです。
# 実行中の nginx プロセスを確認
ps aux | grep nginx
# 確認中に grep 自身が出てきてしまう場合は [n]ginx で回避できる
ps aux | grep "[n]ginx"
# ポート 80 を使っているプロセスを探す
ss -tlnp | grep :80
# journalctl のログをリアルタイムに grep
journalctl -fu nginx | grep "502"
sed:テキストを変換・置換する
sed(Stream EDitor)は、ストリームとして流れるテキストを行単位で編集するコマンドです。最も使うのは s コマンド(substitute)による置換です。
# 基本の置換:最初のマッチのみ
sed 's/old/new/' file.txt
# g フラグで行内の全マッチを置換
sed 's/old/new/g' file.txt
# 大文字小文字を無視して置換(GNU sed)
sed 's/old/new/gi' file.txt
# 特定の行のみ置換(5行目だけ)
sed '5s/old/new/' file.txt
-i でファイルを直接書き換える
-i オプションを付けると、ファイルを直接書き換えます(インプレース編集)。バックアップを作りたい場合は -i.bak のように拡張子を指定します。
# ファイルを直接書き換え(バックアップなし)
sed -i 's/http:/https:/g' config.txt
# バックアップを作りながら書き換え
sed -i.bak 's/http:/https:/g' config.txt
# → config.txt.bak に元のファイルが残る
-i はやり直しが効かないため、重要なファイルには必ず -i.bak を使う習慣をつけましょう。
行の削除
# "DEBUG" を含む行を削除
sed '/DEBUG/d' app.log
# 空行を削除
sed '/^$/d' file.txt
# 先頭の5行を削除
sed '1,5d' file.txt
awk:列の抽出と集計
awk はフィールド(列)ベースのテキスト処理が得意なプログラミング言語兼コマンドです。CSV やログのような「区切り文字で分割されたデータ」を扱うのに向いています。
デフォルトの区切り文字は空白(スペース・タブ)で、各フィールドは $1、$2… と参照します。$0 は行全体を指します。
# /etc/passwd の最初のフィールド(ユーザー名)を表示
awk -F: '{print $1}' /etc/passwd
# -F でフィールド区切りを指定(ここは : )
# カンマ区切り CSV なら -F,
# ディスク使用量の一覧から使用率($5)とマウント先($6)だけ表示
df -h | awk '{print $5, $6}'
NR・NF
NR(Number of Records)は現在の行番号、NF(Number of Fields)は現在の行のフィールド数です。
# 行番号付きで表示
awk '{print NR, $0}' file.txt
# 最後のフィールドだけ表示(フィールド数が行によって異なる場合に便利)
awk '{print $NF}' file.txt
# ヘッダー行(1行目)をスキップ
awk 'NR > 1 {print $0}' data.csv
条件付き出力と集計
# 第3フィールドが 1000 より大きい行だけ表示
awk '$3 > 1000 {print $0}' data.txt
# 第2フィールドの合計を計算
awk '{sum += $2} END {print "合計:", sum}' data.txt
# 行数のカウント(wc -l と同じ)
awk 'END {print NR}' file.txt
パイプラインでの組み合わせ実例
3つのコマンドを組み合わせると、複雑なログ解析もワンライナーで書けます。
以下は Nginx のアクセスログから、HTTP 500 エラーになったリクエスト先 URL の出現回数を集計する例です。
grep " 500 " /var/log/nginx/access.log \
| awk '{print $7}' \
| sort \
| uniq -c \
| sort -rn \
| head -10
処理の流れを追うと:
grep " 500 "… ステータスコード 500 の行だけ絞り込むawk '{print $7}'… Nginx のデフォルトログ形式で7番目フィールドがリクエスト URIsort… 同一 URI をまとめるために並び替えuniq -c… 連続する同じ行をカウントして集約sort -rn… 件数の多い順に並び替えhead -10… 上位10件だけ表示
grep で絞り、awk で列を選び、sort | uniq -c | sort -rn で集計するこのパターンは、ログ解析の定番形式として覚えておく価値があります。
実践的なコツと落とし穴
sed -i の破壊性
前述の通り、-i はファイルを即座に上書きします。複数ファイルへの再帰的な適用(find . -name "*.conf" -exec sed -i ... {} \;)は特に注意が必要です。まず -i なしで出力を確認し、問題なければ -i.bak で書き換えるのが安全な手順です。
ロケールと正規表現方言
grep の正規表現の挙動は、環境変数 LC_ALL や LANG の設定(ロケール)に影響される場合があります。スクリプトで安定した動作を求めるなら LC_ALL=C grep ... のように先頭で固定するのが無難です。また、macOS と Linux(GNU)では sed の -i の挙動が微妙に異なります(macOS では -i '' と書く必要がある)。他者の環境でも動かしたいスクリプトでは注意しましょう。
ripgrep という選択肢
大規模なリポジトリや深いディレクトリツリーを検索するなら、rg(ripgrep)も検討に値します。デフォルトで .gitignore を尊重し、並列処理で grep -r より高速なことが多いです。ただし標準ツールではないので、インフラやサーバー上での利用は grep が無難です。手元の開発環境では rg、本番スクリプトでは grep と使い分けるのが現実的です。
まとめ
grep… パターンで行を絞り込む。-i、-r、-n、-v、-Eを押さえれば大半のユースケースに対応できるsed…s/old/new/gの置換が中心。-i.bakでインプレース編集awk… フィールド処理と集計。-Fで区切り指定、NR/NFとENDブロックを使いこなす
3つを組み合わせたパイプラインは、ログ解析・設定ファイル管理・データ抽出など幅広い場面で役立ちます。最初は「これは grep の仕事か、awk の仕事か」を考えながら使い分けることで、自然と使いどころが身についていきます。
次回(#7)はネットワーク周りに踏み込みます。ip コマンドや ss、curl など、サーバー運用で欠かせないツール群を扱う予定です。