AWKハンドブック
表示
はじめに
[編集]AWKは強力なテキスト処理言語です。ファイルやデータストリームの読み込み、フィールドの抽出、計算、出力など、さまざまな処理を記述できます。AWKスクリプトは簡潔で効率的な方法でデータ操作を行うことができます。
AWKの特徴
[編集]- 行指向のテキスト処理
- パターンマッチングと正規表現
- フィールド分割による列データの処理
- 組み込み変数による柔軟な制御
- 豊富な文字列操作関数
AWKの基本構造
[編集]基本的な構文
[編集]AWKスクリプトは、パターンとアクションのペアから構成されています:
パターン { action } BEGIN { action } END { action }
重要な組み込み変数
[編集]FS
: フィールドセパレータ(デフォルトは空白)RS
: レコードセパレータ(デフォルトは改行)OFS
: 出力フィールドセパレータORS
: 出力レコードセパレータNF
: 現在の行のフィールド数NR
: 現在の行番号FILENAME
: 現在処理中のファイル名
AWKの組み込み関数
[編集]文字列関数
[編集]length(str)
: 文字列の長さを返すsubstr(str, start, length)
: 部分文字列を取り出すindex(str, substr)
: 部分文字列の位置を検索match(str, regexp)
: 正規表現でマッチングsplit(str, array, separator)
: 文字列を配列に分割sub(regexp, replacement)
: 最初のマッチを置換gsub(regexp, replacement)
: すべてのマッチを置換
数値関数
[編集]int(x)
: 整数部分を取得rand()
: 乱数生成sin(x)
,cos(x)
,exp(x)
,log(x)
: 数学関数
実践的なAWKスクリプト例
[編集]/etc/passwdからアカウント一覧を作成
[編集]# ユーザー名とシェルの一覧を表示 BEGIN { FS = ":" print "Username\tShell" print "--------\t-----" } { # パターンを省略した場合全ての行にマッチします # システムアカウントを除外(UIDが1000以上) if ($3 >= 1000) { printf "%-15s\t%s\n", $1, $7 } }
CSVファイルの処理(クォート文字列対応)
[編集]# CSVファイルを処理し、クォートされた文字列も適切に扱う BEGIN { FPAT = "([^,]+)|(\"[^\"]+\")" OFS = "," } { # クォート文字列から引用符を除去 for (i=1; i<=NF; i++) { if ($i ~ /^".*"$/) { $i = substr($i, 2, length($i)-2) } } print }
ログファイル解析
[編集]# Apacheアクセスログから404エラーを抽出し集計 /HTTP\/[0-9.]+ 404/ { urls[$7]++ } END { print "404 Not Found URLs and counts:" for (url in urls) { printf "%-50s %d\n", url, urls[url] } }
正規表現を使った高度な処理
[編集]# メールアドレスを抽出して検証 { email_pattern = "([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})" line = $0 while (match(line, email_pattern)) { email = substr(line, RSTART, RLENGTH) # ドメイン部分を取り出す split(email, parts, "@") printf "Found email: %s (Domain: %s)\n", email, parts[2] line = substr(line, RSTART + RLENGTH) } }
高度なテクニック
[編集]マルチデリミタの処理
[編集]# 空白とカンマの両方をデリミタとして使用 BEGIN { FS = "[ ,]+" # 空白またはカンマの連続をデリミタとする } { for (i=1; i<=NF; i++) { print "Field", i, ":", $i } }
レコードセパレータの活用
[編集]# 段落単位での処理(空行で区切られたテキスト) BEGIN { RS = "\n\n+" # 1つ以上の空行をレコードセパレータとする FS = "\n" # 各行をフィールドとして扱う } { print "Paragraph #" NR ":" for (i=1; i<=NF; i++) { print " Line" i ":" $i } }
配列とソート
[編集]# データの集計とソート出力 { count[$1]++ } END { # 配列のインデックスを取得してソート n = asorti(count, sorted) print "Sorted unique items and their counts:" for (i=1; i<=n; i++) { key = sorted[i] printf "%-20s %d\n", key, count[key] } }
AWKのベストプラクティス
[編集]スクリプトの構造化
[編集]- 明確なBEGINブロックでの初期化
- 適切な変数名の使用
- コメントによるドキュメント化
- 複雑な処理の関数化
効率的な処理
[編集]- 不要な正規表現の最小化
- 大きなファイル処理時のメモリ使用考慮
- 適切なパターンマッチングの選択
デバッグテクニック
[編集]# デバッグ情報の出力 function debug(msg) { printf "DEBUG: %s (Line %d)\n", msg, NR > "/dev/stderr" }
AWK実践イディオム集
[編集]ファイル処理の基本イディオム
[編集]最終行を除く全行に区切り文字を付加
[編集]# カンマ区切りの出力で、最後の要素の後ろにカンマを付けない { printf "%s%s", $0, (NR == FNR ? "" : ",") }
ヘッダー行のスキップ
[編集]# 最初の1行をスキップする3つの方法 NR > 1 { process() } # 方法1 FNR > 1 { process() } # 方法2:複数ファイル対応 !/^Header/ { process() } # 方法3:パターンマッチ
末尾n行の処理
[編集]{ # 最後の10行を保持する循環バッファ buffer[NR % 10] = $0 } END { # 最後の10行を出力 for (i = NR - 9; i <= NR; i++) { print buffer[i % 10] } }
データ加工のイディオム
[編集]列の入れ替え
[編集]# 1列目と2列目を入れ替える { temp = $1 $1 = $2 $2 = temp print }
特定列の抽出(cut相当)
[編集]# カンマ区切りファイルから3,5,7列目を抽出 BEGIN { FS = OFS = "," } { print $3, $5, $7 }
重複行の除去(uniq相当)
[編集]# より効率的なuniq実装 !seen[$0]++ # これだけで重複除去が実現できる # キー列での重複除去 !seen[$1,$2]++ # 1,2列の組み合わせでの重複除去
テキスト処理のイディオム
[編集]行の前後に文字列追加
[編集]# 各行を<li>タグで囲む { print "<li>" $0 "</li>" } # インデント追加 { print " " $0 }
文字列の結合
[編集]# 全行を連結(区切り文字なし) { str = str $0 } END { print str } # カンマ区切りで連結 { if (NR > 1) str = str "," str = str $0 } END { print str }
文字列の分割と結合
[編集]# カンマ区切り文字列をスペース区切りに変換 BEGIN { FS = ","; OFS = " " } { $1 = $1 # この代入で自動的にOFSで結合される print }
計算処理のイディオム
[編集]列の合計と平均
[編集]# 複数列の合計と平均を同時計算 { for (i = 1; i <= NF; i++) { sum[i] += $i count[i]++ } } END { for (i = 1; i <= length(sum); i++) { printf "Column %d: Sum = %f, Average = %f\n", i, sum[i], sum[i]/count[i] } }
最大値・最小値の検出
[編集]# 初期化と更新を簡潔に記述 NR == 1 { min = max = $1 } { if ($1 < min) min = $1 if ($1 > max) max = $1 }
高度な処理イディオム
[編集]2つのファイルの結合(join相当)
[編集]# ファイル1から key-valueをハッシュに格納 FNR == NR { data[$1] = $2 next } # ファイル2を処理しながら結合 { if ($1 in data) { print $0, data[$1] } }
グループ集計
[編集]# キーでグループ化して集計 { count[$1]++ sum[$1] += $2 } END { for (key in count) { printf "%s: count=%d, sum=%f, avg=%f\n", key, count[key], sum[key], sum[key]/count[key] } }
複数区切り文字の処理
[編集]# カンマまたはセミコロンで区切られたデータの処理 BEGIN { # 正規表現をFSに設定 FS = "[,;]" # または FPAT で値のパターンを指定 FPAT = "([^,;]+)|(\"[^\"]+\")" }
ファイル操作のイディオム
[編集]複数ファイルへの出力
[編集]# 条件に応じて異なるファイルに出力 { if ($3 > 100) { print > "high.txt" } else if ($3 > 50) { print > "medium.txt" } else { print > "low.txt" } }
ファイル名をキーにした処理
[編集]# ファイルごとの統計 { count[FILENAME]++ sum[FILENAME] += $1 } END { for (file in count) { printf "%s: records=%d, sum=%f\n", file, count[file], sum[file] } }
デバッグとトレースのイディオム
[編集]デバッグ出力
[編集]# 条件付きデバッグ出力 function debug(msg) { if (DEBUG) { printf "[DEBUG] %s (File=%s, Line=%d)\n", msg, FILENAME, FNR > "/dev/stderr" } }
実行時間計測
[編集]# 処理時間の計測 BEGIN { start = systime() } END { printf "Processing time: %d seconds\n", systime() - start > "/dev/stderr" }
メモリ使用量の監視
[編集]# 配列サイズの監視 function check_memory(arr, msg) { printf "Array size (%s): %d\n", msg, length(arr) > "/dev/stderr" }
エラー処理のイディオム
[編集]入力検証
[編集]# データ型と範囲のチェック { if ($1 !~ /^[0-9]+$/) { printf "Error: Invalid number at line %d\n", NR > "/dev/stderr" next } if ($2 < 0 || $2 > 100) { printf "Error: Value out of range at line %d\n", NR > "/dev/stderr" next } }
終了状態の設定
[編集]# エラー発生時の終了コード設定 { if (error_condition) { print "Error message" > "/dev/stderr" exit 1 } }
これらのイディオムは、実際の開発現場で頻繁に使用される定型的なパターンです。状況に応じて適切に組み合わせることで、効率的なAWKプログラミングが可能になります。
AWKベストプラクティスガイド
[編集]スクリプト構造とスタイル
[編集]基本的なスクリプト構造
[編集]#!/usr/bin/awk -f # メタ情報 # Author: name # Date: YYYY-MM-DD # Description: スクリプトの目的 BEGIN { # 初期化 initialize() } # メイン処理 function initialize() { # 変数の初期化 FS = "," OFS = "," TOTAL = 0 } # パターンとアクションを明確に分離 /pattern/ { process_record() } END { # 後処理 finalize() }
命名規則
[編集]# 定数は大文字 BEGIN { MAX_RECORDS = 1000 DEFAULT_VALUE = -1 } # 関数名は動詞から始める function calculate_average(values, length) { # ローカル変数は小文字 local_sum = 0 for (i = 1; i <= length; i++) { local_sum += values[i] } return local_sum / length }
エラー処理とバリデーション
[編集]入力データの検証
[編集]{ if (!validate_record($0)) { report_error("Invalid record format at line " NR) next } process_valid_record() } function validate_record(line) { if (NF != EXPECTED_FIELDS) { return 0 } if ($1 !~ /^[0-9]+$/) { return 0 } return 1 } function report_error(message) { printf("ERROR: %s\n", message) > "/dev/stderr" }
優雅なエラー処理
[編集]function process_file(filename) { if ((getline < filename) < 0) { printf("ERROR: Cannot open file %s\n", filename) > "/dev/stderr" exit 1 } close(filename) } function safe_divide(numerator, denominator, result) { # 局所変数としてresultを使用 if (denominator == 0) { return DEFAULT_VALUE } result = numerator / denominator return result }
パフォーマンス最適化
[編集]効率的なパターンマッチング
[編集]# 悪い例 { if ($0 ~ /pattern1/) process1() if ($0 ~ /pattern2/) process2() if ($0 ~ /pattern3/) process3() } # 良い例 /pattern1/ { process1() } /pattern2/ { process2() } /pattern3/ { process3() }
メモリ使用の最適化
[編集]# 大きなファイル処理時のメモリ管理 { # 必要なデータのみを保持 key = $1 if (key in cache) { if (length(cache) > MAX_CACHE_SIZE) { delete cache[get_oldest_key()] } cache[key] = $2 } } function get_oldest_key( k) { # 局所変数kを使用 for (k in cache) { return k # 最初のキーを返す } }
モジュール化と再利用性
[編集]関数の適切な分割
[編集]# メイン処理を小さな関数に分割 { if (validate_input()) { process_record() update_statistics() generate_output() } } function validate_input() { return NF == EXPECTED_FIELDS } function process_record() { calculate_values() update_totals() } function update_statistics() { update_min_max() update_average() }
設定の外部化
[編集]# 設定を初期化ブロックにまとめる BEGIN { # 設定値の集中管理 Config["field_separator"] = "," Config["date_format"] = "%Y-%m-%d" Config["max_records"] = 1000 # 設定の適用 apply_configuration() } function apply_configuration() { FS = Config["field_separator"] OFS = Config["field_separator"] }
デバッグとトレーサビリティ
[編集]包括的なロギング
[編集]function log_debug(message) { if (DEBUG) { printf("[DEBUG] %s:%d - %s\n", FILENAME, FNR, message) > "/dev/stderr" } } function log_error(message) { printf("[ERROR] %s:%d - %s\n", FILENAME, FNR, message) > "/dev/stderr" } function log_info(message) { printf("[INFO] %s\n", message) > "/dev/stderr" }
デバッグ支援機能
[編集]function dump_array(arr, prefix, i) { # 配列の内容をデバッグ出力 for (i in arr) { log_debug(sprintf("%s[%s] = %s", prefix, i, arr[i])) } } function trace_call(func_name) { # 関数呼び出しのトレース TRACE_DEPTH++ printf("%*s-> %s\n", TRACE_DEPTH * 2, "", func_name) > "/dev/stderr" }
セキュリティ考慮事項
[編集]入力のサニタイズ
[編集]function sanitize_input(str) { # 危険な文字を除去 gsub(/[;&|]/, "", str) return str } function validate_filename(filename) { # ファイル名の検証 if (filename ~ /[^[:alnum:]._-]/) { return 0 } return 1 }
安全なファイル操作
[編集]function safe_open_file(filename, status) { if (!validate_filename(filename)) { log_error("Invalid filename: " filename) return 0 } status = (getline < filename) >= 0 close(filename) return status }
テスト可能性
[編集]ユニットテスト用の構造
[編集]# テスト用の関数 function test_calculate_average() { # テストデータの準備 test_values[1] = 10 test_values[2] = 20 test_values[3] = 30 # テストの実行 result = calculate_average(test_values, 3) # 結果の検証 if (result != 20) { log_error("Test failed: expected 20, got " result) return 0 } return 1 }
統合テストのサポート
[編集]# テストモードの実行 BEGIN { if (TEST_MODE) { run_tests() exit } } function run_tests( test_count, passed) { test_count = passed = 0 # テストの実行 test_count++ if (test_calculate_average()) passed++ # テスト結果の報告 printf("%d/%d tests passed\n", passed, test_count) > "/dev/stderr" }
ドキュメント化
[編集]コードドキュメント
[編集]# 関数のドキュメント # Parameters: # values - 数値の配列 # length - 配列の長さ # Returns: # 計算された平均値、エラー時は-1 function calculate_average(values, length) { # 実装 }
使用方法の説明
[編集]function print_usage() { print "Usage: awk -f script.awk [options] file" > "/dev/stderr" print "Options:" > "/dev/stderr" print " -v DEBUG=1 Enable debug output" > "/dev/stderr" print " -v TEST_MODE=1 Run tests" > "/dev/stderr" }
これらのベストプラクティスを適用することで、より保守性が高く、信頼性のあるAWKスクリプトを作成することができます。状況に応じて適切なプラクティスを選択し、組み合わせることが重要です。
さいごに
[編集]AWKは単純なテキスト処理から複雑なデータ解析まで、幅広いタスクをこなすことができます。本ハンドブックで紹介した例や技術を基に、実際の問題解決に活用してください。
参考文献
[編集]- "The AWK Programming Language" (Aho, Weinberger & Kernighan)
- "GAWK: Effective AWK Programming"
- "Sed & Awk" (Dale Dougherty)