0Pricing
Linux Command Line & Bash Scripting Mastery · レッスン

awk関数、printfによる書式設定、レポート生成

ユーザー定義関数を作成し、printfで生データのストリームから整った表形式のレポートを出力します。

「awk関数、printfによる書式設定、レポート生成」はCoddyKit上の無料Linux Command Line & Bash Scripting Masteryレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLinux Command Line & Bash Scripting Mastery学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

awkで関数とprintfが重要な理由

awkプログラムが1行のコマンドから複数ステップのパイプラインへと成長すると、2つの機能が不可欠になります。それはユーザー定義関数とprintfによる書式設定です。

関数を使うと、パーセンテージ計算、文字列のトリミング、単位変換など、再利用可能なロジックをカプセル化できます。同じ処理を1度だけ記述し、同じプログラム内のどこからでも呼び出せます。printfを使えば、列幅、小数点以下の桁数、余白、配置など、出力の見た目を正確に制御できます。

この2つを組み合わせると、生のログ行を、エンジニアやマネージャーが活用できる、整った読みやすいレポートに変換できます。

  • 関数は重複を減らし、プログラムを単体でテストしやすくします。
  • printfはデータを固定幅の列に揃えるため、入力の長さが異なってもレポートの読みやすさを保てます。
  • どちらもPOSIX awk、gawk、mawkで動作し、拡張機能は必要ありません。

awkでユーザー関数を定義する

ユーザー関数はfunctionキーワードで宣言し、パターンとアクションの規則の前後どちらに置いてもかまいません。構文は次のとおりです。

function name(param1, param2,    local1, local2) {
    # body
    return value
}

awkの重要な慣用表現として、ローカル変数は、間に余分な空白を入れた追加のパラメーターとして記述します。localキーワードはありません。余分な空白によって、それらのパラメーターが呼び出し側から渡される引数ではなく、ローカル変数であることを示します。

  • パラメーターとして宣言されていない変数は、デフォルトでグローバルです。
  • 関数は自分自身を再帰的に呼び出せます。
  • returnは省略できます。省略した場合、関数は空文字列を返します。
#!/usr/bin/env bash
# Demonstrate a simple awk user function
echo '10 3
7 0
100 4' | awk '
function divide(a, b,    result) {
    if (b == 0) return "ERR"
    result = a / b
    return result
}
{
    print $1 "/" $2 " = " divide($1, $2)
}
'

文字列処理を行う関数

関数は、繰り返し行う文字列操作に特に役立ちます。たとえば、前後の空白のトリミングを考えてみましょう。これは、生のログデータを比較やレポートに使う前によく必要になる処理です。

次の例では、gsubを使ってtrim(s)を定義し、すべてのレコードで使用しています。これにより、メインの規則を簡潔で読みやすく保てます。

  • gsub(regex, replacement, target)はtargetをその場で変更し、置換回数を返します。
  • 正規表現のロジックを関数内に置くと、規則ブロックを本来の処理に集中させられます。
  • BEGINブロック内で手作りの文字列を直接渡すことで、単体テストを行えます。
#!/usr/bin/env bash
# trim() strips leading/trailing spaces; used to normalise CSV-like data
printf '  alice , 90 \n bob , 85 \n  carol , 92 \n' | awk -F',' '
function trim(s) {
    gsub(/^[ \t]+|[ \t]+$/, "", s)
    return s
}
{
    name  = trim($1)
    score = trim($2)
    print name, score
}
'

awkのprintf入門

awkのprintfは、CやBash組み込みのprintfと同じ規則に従います。printとの主な違いは、printfが自動的に改行を追加しないことです。自分で\nを追加する必要があります。

よく使う書式指定子:

  • %s — 文字列
  • %d — 整数
  • %f — 浮動小数点数
  • %e — 指数表記
  • %g — %fと%eのうち短い形式

幅と精度は、%と指定子の間に数値を置いて指定します。%-20sは20文字幅のフィールドで左揃えにし、%8.2fは幅8文字、小数点以下2桁の浮動小数点数を指定します。

#!/usr/bin/env bash
# printf format specifiers in awk
echo '' | awk '
BEGIN {
    printf "%s\n",        "plain string"
    printf "%10s\n",     "right-pad"
    printf "%-10s|\n",   "left-pad"
    printf "%8.2f\n",    3.14159
    printf "%08d\n",     42
    printf "%e\n",       123456.789
}
'

printfでレポートのヘッダーを作成する

整ったレポートには、ヘッダー行と区切り線が必要です。BEGINブロックは、入力の処理前に1回だけ実行されるため、これらを出力するのに適しています。

ポイントは、ヘッダーの幅をデータ行で使う幅と正確に一致させることです。BEGIN(または関数)で幅の定数を定義しておくと、後から列幅を調整しても全体の整合性を保てます。

  • ダッシュの区切り文字列をprintfで出力し、区切り線を描きます。
  • ヘッダー行は必ず\nで終わらせます。
  • BEGINのヘッダー、レコードごとの行、ENDのフッターを組み合わせて、完全なレポート構造を作ります。
#!/usr/bin/env bash
# Print a report header in BEGIN, data rows per-record, total in END
printf 'alice 9200 12\nbob 8750 10\ncarol 10400 14\n' | awk '
BEGIN {
    printf "%-10s %10s %6s %12s\n", "Name", "Salary", "Months", "Annual"
    printf "%s\n", "----------------------------------------------"
    total = 0
}
{
    annual = $2 * $3
    total += annual
    printf "%-10s %10d %6d %12d\n", $1, $2, $3, annual
}
END {
    printf "%s\n", "----------------------------------------------"
    printf "%-10s %10s %6s %12d\n", "TOTAL", "", "", total
}
'

書式設定ロジックにユーザー関数を使う

複数列のレポートを作成すると、パーセンテージバーの表示やバイト数の人間に読みやすい単位への変換など、同じ書式設定処理を複数箇所で呼び出すことがよくあります。これを関数内に置くと重複を避けられ、書式を全体的に簡単に変更できます。

次の関数はバイト数をKB、MB、またはGBに変換し、書式設定済みの文字列を返します。呼び出し側の規則は$NF(最後のフィールド)を渡し、返された値をそのまま出力するだけです。

#!/usr/bin/env bash
# human_bytes() converts raw byte counts to KB/MB/GB strings
printf 'var.log 1024\naccess.log 2097152\ncore.dump 1073741824\ntiny.txt 512\n' | awk '
function human_bytes(n,    s) {
    if (n >= 1073741824) { s = sprintf("%.1f GB", n/1073741824) }
    else if (n >= 1048576) { s = sprintf("%.1f MB", n/1048576) }
    else if (n >= 1024)    { s = sprintf("%.1f KB", n/1024) }
    else                   { s = sprintf("%d B",    n) }
    return s
}
BEGIN { printf "%-20s %10s\n", "File", "Size"; print "------------------------------" }
{ printf "%-20s %10s\n", $1, human_bytes($2) }
'

印刷前に配列へデータを蓄積する

実際のレポートでは、入力をすべて確認するまで計算できない合計、平均、ランキングなどの値が必要になることがよくあります。このパターンでは、次のように処理します。

  • レコードごとの処理中に、連想配列へデータを蓄積します。
  • ENDで派生値(平均や割合など)を計算します。
  • 整形した出力はENDからのみ出力し、データセット全体に合わせて幅を調整できるようにします。

この遅延出力パターンは、awkでレポートを生成する際の非常に強力なイディオムの一つです。以下の例では、アクセスログからHTTPステータスコードごとのリクエスト数とレスポンスバイト数を集計します。

#!/usr/bin/env bash
# Summarise an nginx-style access log by HTTP status code
printf '127.0.0.1 200 1234\n127.0.0.1 404 512\n10.0.0.2 200 8900\n10.0.0.3 500 256\n10.0.0.4 200 4096\n10.0.0.5 404 300\n' | awk '
{
    status  = $2
    bytes   = $3
    count[status]++
    total_bytes[status] += bytes
}
END {
    printf "%-8s %8s %14s\n", "Status", "Requests", "Total Bytes"
    print  "-----------------------------------"
    for (s in count)
        printf "%-8s %8d %14d\n", s, count[s], total_bytes[s]
}
'

awkの再帰関数

awkは再帰をサポートしています。典型的な用途は、階乗の計算や、端末の幅に合わせたダッシュの行のような、繰り返し文字列の生成です。再帰は汎用言語ほどawkで頻繁に使われるわけではありませんが、正しく動作し、主要な実装で利用できます。

以下の例では、再帰的なrepeat(s, n)関数を定義し、最も幅の広いデータ行に自動的に合わせた区切り線を描画します。ダッシュの数をハードコードする必要はありません。

  • 入力が大きい場合は、深い再帰を避けてください。awkには末尾呼び出し最適化がありません。
  • 単純な繰り返しには、sprintf("%*s", n, "")とgsub(/ /, "-")を使うほうが高速ですが、説明例としては分かりにくくなります。
#!/usr/bin/env bash
# Recursive repeat() to build separator lines dynamically
awk '
function repeat(s, n,    acc) {
    if (n <= 0) return ""
    acc = s repeat(s, n-1)
    return acc
}
BEGIN {
    header = sprintf("%-15s %10s %10s", "Metric", "Current", "Target")
    sep    = repeat("-", length(header))
    print sep
    print header
    print sep
    printf "%-15s %10.1f %10.1f\n", "CPU %",     72.4, 60.0
    printf "%-15s %10.1f %10.1f\n", "Mem GB",    14.2, 16.0
    printf "%-15s %10d %10d\n",   "Open FDs", 1024,  800
    print sep
}
' /dev/null

awk内でファイルまたはパイプラインにprintfする

awkのprintf(およびprint)では、awkプログラムの内部から直接、ファイルへ出力をリダイレクトしたり、シェルコマンドへパイプしたりできます。処理全体をサブシェルでラップする必要はありません。

  • printf "..." > "file.txt" — ファイルへ書き込みます(1回だけ切り詰め、その後は同じ実行中に追記します)。
  • printf "..." >> "file.txt" — ファイルへ追記します。
  • printf "..." | "sort -rn" — シェルコマンドへパイプします。awkはレコード間でパイプを開いたままにし、プログラム終了時に閉じます。

よく使われるパターンは、ログを1回だけ走査し、ステータスごと、またはホストごとに複数の出力ファイルへ分割する方法です。大きなファイルを何度も走査せずに済みます。

#!/usr/bin/env bash
# Route records into per-status files using print redirection
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT

printf '200 /index.html\n404 /missing\n200 /api/data\n500 /crash\n404 /lost\n' | awk -v out="$tmpdir" '
{
    file = out "/" $1 ".log"
    printf "%-6s %s\n", $1, $2 > file
}
END {
    close(file)  # flush all open handles
}
'

echo "=== 200 ==="
cat "$tmpdir/200.log"
echo "=== 404 ==="
cat "$tmpdir/404.log"

awkでCSVレポートを生成する

すべてのレポートが人間向けとは限りません。スプレッドシートや後続のパイプラインに渡すため、機械可読なCSVとして出力しなければならない場合もあります。awkのprintfなら、OFSをカンマに設定するか、区切り文字をすべて明示的に制御して、きれいに処理できます。

awkで堅牢なCSVを生成するための重要なルールは2つあります。

  • カンマや改行を含む可能性があるフィールドは、二重引用符で囲みます。
  • フィールド内のリテラルな二重引用符は、二重にしてエスケープします。例:He said ""hello""。

以下のcsv_field(s)関数はこの引用処理をまとめたもので、すべての文字列フィールドに一貫して適用できます。

#!/usr/bin/env bash
# Emit a valid CSV report from space-separated input
printf 'alice engineer 95000\nbob "sales,mgr" 82000\ncarol developer 110000\n' | awk '
function csv_field(s,    safe) {
    safe = s
    gsub(/"/, "\"\"" , safe)   # double any embedded quotes
    return "\"" safe "\""       # wrap in quotes
}
BEGIN { print "Name,Role,Salary" }
{
    printf "%s,%s,%d\n", csv_field($1), csv_field($2), $3
}
'

最初から最後まで:完全なアクセスログレポート

この最後の例では、ユーザー関数、printfによる整形、配列へのデータ蓄積、構造化されたヘッダーとフッターをすべて組み合わせます。入力は簡略化したWebアクセスログで、出力はメソッドごとの合計と総合計行を含む、人間が読みやすい集計表です。

使用している主なテクニック:

  • count[method]++とbytes[method] += sizeで、1回の走査中にメソッドごとの統計を蓄積します。
  • human_bytes()(前のシーンで扱った関数)で、バイト数の合計を変換します。
  • ヘッダー、データ、フッターで幅を一致させたprintfを使うことで、入力の大きさにかかわらず表の整列を保ちます。
  • ENDブロックでfor (k in arr)を使って配列を反復し、sortへのパイプでソートした出力を生成します。
#!/usr/bin/env bash
# Full access log report: method breakdown with human-readable bytes
printf 'GET /index 200 4096\nPOST /api 201 512\nGET /img 200 102400\nDELETE /item 204 0\nPOST /login 401 256\nGET /style 200 8192\n' | awk '
function human_bytes(n,    s) {
    if (n >= 1048576) s = sprintf("%.1f MB", n/1048576)
    else if (n >= 1024) s = sprintf("%.1f KB", n/1024)
    else s = sprintf("%d B", n)
    return s
}
{
    method = $1
    size   = $4
    count[method]++
    bytes[method] += size
    grand_count++
    grand_bytes += size
}
END {
    fmt = "%-10s %8s %15s\n"
    sep = "----------------------------------"
    printf fmt, "Method", "Requests", "Bytes"
    print sep
    for (m in count)
        printf "%-10s %8d %15s\n", m, count[m], human_bytes(bytes[m])
    print sep
    printf "%-10s %8d %15s\n", "TOTAL", grand_count, human_bytes(grand_bytes)
}
'

理解度チェック:awk関数のローカル変数

POSIXの慣例に従って、awk関数内でresultをローカル変数として正しく宣言しているものは次のうちどれですか?

レッスンのまとめ:awk関数、printf、レポート生成

このレッスンでは、生データのストリームから整形されたレポートを生成する、実用的な品質のawkプログラムの書き方を学びました。次の点を覚えておきましょう。

  • ユーザー関数はfunction name(params, locals)で宣言します。ローカル変数のパラメーターの前に余分な空白を入れるのがPOSIXの慣例であり、localキーワードはありません。
  • printfを使うと、出力の幅(%10s)、配置(%-10s)、精度(%8.2f)を正確に制御できます。\nは明示的に追加してください。
  • ヘッダーや区切り線の出力にはBEGINを、データの蓄積にはレコードごとのルールを、合計の計算と完成したレポートの出力にはENDを使います。
  • awkの内部からprintfの出力をファイル(> file)やパイプライン(| "sort")へリダイレクトし、レポートを分割したり出力を後処理したりできます。
  • CSVを出力するときは、文字列フィールドをcsv_field()ヘルパーで囲み、フィールド内のカンマや引用符を安全に処理します。
  • 単位を変換する関数(human_bytes)、文字を繰り返す関数(repeat)、文字列を整える関数(trim、csv_field)は、個人用のawkライブラリに保存しておく価値があります。プロジェクトをまたいで簡単に組み合わせて使えます。

よくある質問

「awk関数、printfによる書式設定、レポート生成」レッスンは無料ですか?

はい。「awk関数、printfによる書式設定、レポート生成」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Linux Command Line & Bash Scripting Masteryコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Linux Command Line & Bash Scripting Masteryコースには全4レッスンが含まれています。

「awk関数、printfによる書式設定、レポート生成」で何を学びますか?

ユーザー定義関数を作成し、printfで生データのストリームから整った表形式のレポートを出力します。 ブラウザで直接実行するハンズオンコードでLinux Command Line & Bash Scripting Masteryを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Linux Command Line & Bash Scripting Masteryを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLinux Command Line & Bash Scripting Masteryは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「awk関数、printfによる書式設定、レポート生成」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLinux Command Line & Bash Scripting Masteryレッスンでコードを書いて実行できますか?

はい。すべてのLinux Command Line & Bash Scripting Masteryレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. awkのレコード、フィールド、カスタム区切り文字
  2. パターン、範囲、BEGIN/ENDブロック
  3. awk配列とグループ化による集計
  4. awk関数、printfによる書式設定、レポート生成
← Linux Command Line & Bash Scripting Masteryに戻る