grep・awkの応用テクニック - find/grep/awkマスターシリーズ応用編

grep・awkの応用テクニック - find/grep/awkマスターシリーズ応用編

応用編では、grep の環境変数最適化・次世代高速ツール活用と、awk の連想配列・ユーザー定義関数・ストリーム処理を学ぶ。プロフェッショナルレベルのデータ処理技術を習得する。

grepコマンド:テキスト検索の魔法使い

grep は 「Global Regular Expression Print」 の略で、ファイルや入力から特定のパターンにマッチする行を抽出するコマンド。正規表現と組み合わせることで非常に強力な検索ツールになる。

基本構文

grep [オプション] パターン ファイル名

基本的な使い方

文字列検索:

# "Linux"という文字列を含む行を表示
grep "Linux" document.txt

# 大文字小文字を区別せずに検索
grep -i "linux" document.txt

# "error"を含まない行を表示(逆検索)
grep -v "error" log.txt

行番号と文脈表示:

# マッチした行の行番号も表示
grep -n "function" script.js

# マッチした行の前後3行も表示
grep -C 3 "ERROR" app.log

# マッチした行の前1行、後2行を表示
grep -A 2 -B 1 "WARNING" app.log

ファイル検索とカウント:

# "TODO"を含むファイル名のみを表示
grep -l "TODO" *.js

# "error"を含む行数をカウント
grep -c "error" log.txt

# ディレクトリを再帰的に検索(注意: /etc/ には機密情報が含まれる場合があります)
grep -r "password" /etc/

正規表現との組み合わせ

grep の真の力は 正規表現 との組み合わせで発揮される。

# 行の始まりが"Linux"の行を検索
grep "^Linux" document.txt

# 行の終わりが"finished"の行を検索
grep "finished$" log.txt

# 空行を検索
grep "^$" file.txt

# 1つ以上の数字を含む行を検索
grep -E "[0-9]+" numbers.txt

# "color"または"colour"を検索
grep -E "colou?r" text.txt

# 複数のパターンのいずれかにマッチ(OR検索)
grep -E "error|warning|fatal" log.txt

実用パターン例:

# IPアドレスパターンを検索
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log

# メールアドレスパターンを検索
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt

# 日付パターン(YYYY-MM-DD)を検索
grep -E "20[0-9]{2}-[0-1][0-9]-[0-3][0-9]" log.txt

grepとパイプの組み合わせ

他のコマンドとパイプで繋ぐことで、強力なデータ処理パイプラインを構築できる。

# nginxプロセスのみを表示
ps aux | grep "nginx"

# grep自身を除外してpythonプロセスを表示
ps aux | grep -v "grep" | grep "python"

# リアルタイムでエラーを監視
tail -f /var/log/app.log | grep --line-buffered "ERROR"

# 404エラーの発生回数をカウント
cat access.log | grep "404" | wc -l

# ポート80で待ち受けているプロセスを表示
netstat -an | grep ":80 "

高速化テクニック

環境変数とロケール最適化 で大容量ファイル処理が高速化する。

# UTF-8処理オーバーヘッドを排除(最大10倍高速化)
LC_ALL=C grep "ERROR" huge_log.txt

# バイナリファイルをスキップ
LC_ALL=C grep --binary-files=without-match "pattern" /var/log/*

# 色付けを無効化してさらに高速化
GREP_OPTIONS="--color=never" LC_ALL=C grep -F "ERROR" *.log

# 固定文字列検索(正規表現処理を無効化)
grep -F "literal_string" file.txt

次世代grep:ripgrepとagの活用

従来の grep より高速で多機能な代替ツール。

ripgrep(rg)- Rust製高速grep:

# JavaScriptファイルのみを対象に高速検索
rg --type js "function" /var/www/

# JSON出力で構造化データとして処理
rg --json "ERROR" /var/log/ | jq '.data.lines.text'

# 検索統計と件数を同時表示
rg --stats --count "TODO" ./src/

ag(The Silver Searcher):

# マルチコア並列処理で大容量検索
ag --parallel "pattern" /large/directory/

# 前後5行表示でグループ化
ag --context=5 --group "ERROR" /var/log/

パフォーマンス比較(1GBファイル検索):

ツール 実行時間 メモリ使用量 特徴
grep 15.2秒 2MB 標準、安定
LC_ALL=C grep 8.1秒 2MB 高速化済み
ripgrep (rg) 2.3秒 8MB 最高速、多機能
ag 4.1秒 12MB 高速、開発者向け

大容量ファイル処理

# リアルタイムでログを監視しながら検索
tail -f /var/log/huge.log | grep --line-buffered "ERROR"

# gzip圧縮ファイルを解凍せずに検索
zgrep "ERROR" /var/log/app.log.gz

# bzip2ファイルも直接検索可能
bzgrep "pattern" archive.log.bz2

# 大容量ファイルを分割して並列処理
split -l 1000000 huge.log chunk_ && grep "ERROR" chunk_* | sort

レポート生成

# エラーレポートをCSV形式で生成
grep -n "ERROR" *.log | awk -F: '{print $1","$2","$3}' > error_report.csv

# 包括的なエラー分析レポート生成
{
  echo "=== ERROR分析レポート $(date) ==="
  echo "総エラー数: $(grep -c ERROR app.log)"
  echo "Top 5 エラー:"
  grep -o 'ERROR.*' app.log | sort | uniq -c | sort -nr | head -5
}

awkコマンド:データ処理の魔術師

awk は 「Alfred Aho, Peter Weinberger, Brian Kernighan」 の頭文字から名付けられた、強力なテキスト処理言語。CSVファイルやログファイルの処理で真価を発揮する。

awkの考え方

awk は入力を レコード(通常は行)と フィールド(通常は列)に分けて処理する。

名前,年齢,職業
田中,25,エンジニア
佐藤,30,デザイナー
山田,28,マネージャー
  • $1: 1番目のフィールド(名前)
  • $2: 2番目のフィールド(年齢)
  • $3: 3番目のフィールド(職業)
  • $0: レコード全体
  • NF: フィールド数
  • NR: レコード番号

基本構文

awk 'パターン { アクション }' ファイル

パターンにマッチする行に対してアクションを実行する。

基本的なawk操作

列の抽出:

# 1列目(名前)のみを表示
awk '{print $1}' employees.csv

# 2列目と3列目を表示
awk '{print $2, $3}' employees.csv

# 行番号付きで全体を表示
awk '{print NR ": " $0}' file.txt

区切り文字の指定:

# カンマ区切りファイルの1列目を表示
awk -F ',' '{print $1}' data.csv

# コロン区切りのユーザー名とUIDを表示
awk -F ':' '{print $1, $3}' /etc/passwd

# タブ区切りファイルの2列目を表示
awk 'BEGIN {FS="\t"} {print $2}' tab_separated.txt

条件付き処理:

# 年齢が25歳より上の人の名前と年齢を表示
awk '$2 > 25 {print $1, $2}' employees.csv

# 職業がエンジニアの人の名前を表示
awk '$3 == "エンジニア" {print $1}' employees.csv

# フィールド数が3より多い行を行番号付きで表示
awk 'NF > 3 {print NR, $0}' data.txt

計算・集計処理

基本的な計算:

# 3列目の合計を計算
awk '{sum += $3} END {print "合計:", sum}' sales.csv

# 2列目の平均値を計算
awk '{sum += $2; count++} END {print "平均:", sum/count}' ages.txt

# 2列目の最大値を求める
awk 'BEGIN {max=0} {if($2>max) max=$2} END {print "最大値:", max}' numbers.txt

グループ別集計:

# 部署別の給与合計を計算
awk '{dept[$3] += $2} END {for (d in dept) print d, dept[d]}' salary.csv

# IPアドレス別のアクセス回数をカウント
awk '{count[$1]++} END {for (c in count) print c, count[c]}' access.log

複雑な処理例:

# 地域別の売上統計(合計、件数、平均)
awk -F, 'NR>1 {sales[$2]+=$4; count[$2]++} END {for(region in sales) printf "%s: 売上%d 件数%d 平均%.1f\n", region, sales[region], count[region], sales[region]/count[region]}' sales_data.csv

BEGIN・ENDパターン

  • BEGIN: ファイル処理の 開始前 に実行
  • END: ファイル処理の 終了後 に実行
# ヘッダーを出力してからデータを処理
awk 'BEGIN {print "処理開始", "氏名", "年齢"} {print NR, $1, $2}' data.txt

# 処理後に総レコード数を表示
awk '{count++} END {print "総レコード数:", count}' data.txt

# レポート形式で売上集計
awk 'BEGIN {print "=== 売上レポート ==="} {total+=$3} END {print "総売上:", total, "円"}' sales.txt

高度な活用法

# 複数ファイルをファイル名付きで処理
awk 'FNR==1{print "=== " FILENAME " ==="} {print NR, $0}' file1.txt file2.txt

# 条件に応じて判定結果を追加
awk '{if($2>=60) grade="合格"; else grade="不合格"; print $1, $2, grade}' scores.txt

連想配列の完全活用

awk の真の力は 連想配列(ハッシュテーブル) にある。多次元データ処理で威力を発揮する。

多次元集計(地域×月別売上):

awk -F, '
NR>1 {
    sales[$2][$3] += $4;
    total_by_region[$2] += $4;
    total_by_month[$3] += $4;
    grand_total += $4;
}
END {
    printf "%-12s", "地域/月";
    for (month in total_by_month) printf "%10s", month;
    printf "%12s\n", "地域計";

    for (region in total_by_region) {
        printf "%-12s", region;
        for (month in total_by_month) {
            printf "%10d", (month in sales[region]) ? sales[region][month] : 0;
        }
        printf "%12d\n", total_by_region[region];
    }
}' sales_data.csv

ユーザー定義関数

複雑な処理を関数化して再利用し、保守性の高いコードを作成する。

統計計算ライブラリ:

awk '
function average(arr, count,    sum, i) {
    sum = 0;
    for (i = 1; i <= count; i++) sum += arr[i];
    return sum / count;
}

function stddev(arr, count,    avg, sum_sq, i) {
    avg = average(arr, count);
    sum_sq = 0;
    for (i = 1; i <= count; i++) {
        sum_sq += (arr[i] - avg) ^ 2;
    }
    return sqrt(sum_sq / count);
}

{
    if (NF >= 2 && $2 ~ /^[0-9]+\.?[0-9]*$/) {
        values[++count] = $2;
        sum += $2;
    }
}

END {
    if (count > 0) {
        printf "n=%d\n", count;
        printf "平均値:   %.2f\n", average(values, count);
        printf "標準偏差: %.2f\n", stddev(values, count);
    }
}' numerical_data.txt

ストリーム処理とgetline

リアルタイムデータ処理や外部コマンド連携で真価を発揮する。

リアルタイムログ監視:

tail -f /var/log/apache2/access.log | awk '
BEGIN {
    window_size = 300;
    alert_threshold = 100;
}
{
    "date +%s" | getline current_time;
    close("date +%s");

    access_times[current_time]++;

    for (time in access_times) {
        if (current_time - time > window_size) {
            delete access_times[time];
        }
    }

    total_access = 0;
    for (time in access_times) {
        total_access += access_times[time];
    }

    if (total_access > alert_threshold) {
        printf "[ALERT] High traffic: %d requests in last 5 minutes\n", total_access;
    }
}'

パフォーマンス最適化

awkの高速化テクニック

  • 不要な文字列結合を避ける(配列を使う)
  • 大きなデータは定期的に delete でクリア
  • 必要なフィールドのみを処理
  • BEGIN ブロックで定数を初期化

メモリ効率的な大容量ファイル処理:

awk '
BEGIN {
    processed = 0;
    batch_size = 10000;
}
{
    process_record($0);
    processed++;

    if (processed % batch_size == 0) {
        cleanup_memory();
        printf "処理中: %d レコード完了\n", processed > "/dev/stderr";
    }
}

function process_record(record,    fields) {
    split(record, fields, ",");
    if (fields[2] > threshold) {
        summary[fields[1]] += fields[3];
    }
}

function cleanup_memory(    key) {
    for (key in old_cache) delete old_cache[key];
}

END {
    for (key in summary) printf "%s: %d\n", key, summary[key];
}' huge_data_file.csv

高度な出力フォーマッティング

アスキーアート・チャート生成:

awk -F, '
NR > 1 { sales[$1] += $3; }
END {
    max_sales = 0;
    for (person in sales) {
        if (sales[person] > max_sales) max_sales = sales[person];
    }
    chart_width = 50;
    scale = max_sales / chart_width;

    print "営業成績チャート";
    print "================";

    for (person in sales) {
        bar_length = int(sales[person] / scale);
        printf "%-10s |", person;
        for (j = 1; j <= bar_length; j++) printf "█";
        printf " %d万円\n", sales[person] / 10000;
    }
}' sales_report.csv

次のステップ

応用編で学んだ grep と awk の究極テクニックを、実践編でさらに深める。