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/nullawk内でファイルまたはパイプラインに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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- awkのレコード、フィールド、カスタム区切り文字
- パターン、範囲、BEGIN/ENDブロック
- awk配列とグループ化による集計
- awk関数、printfによる書式設定、レポート生成