find/grep/awkマスターシリーズ 応用編
grep/awkの究極テクニック
grepの環境変数最適化・次世代高速ツール活用と、awkの連想配列・ユーザー定義関数・ストリーム処理を学ぶ応用編。プロフェッショナルレベルのデータ処理技術を習得します。
4. grepコマンド:テキスト検索の魔法使い
grepは「Global Regular Expression Print」の略で、ファイルや入力から特定のパターンにマッチする行を抽出するコマンドです。正規表現と組み合わせることで、非常に強力な検索ツールになります。
🔧 基本構文
grep [オプション] パターン ファイル名
指定したパターンを含む行を表示
🔰 基本的な使い方
文字列検索
grep "Linux" document.txt
"Linux"という文字列を含む行を表示
grep -i "linux" document.txt
大文字小文字を区別せずに検索
grep -v "error" log.txt
"error"を含まない行を表示(逆検索)
行番号と文脈表示
grep -n "function" script.js
マッチした行の行番号も表示
grep -C 3 "ERROR" app.log
マッチした行の前後3行も表示
grep -A 2 -B 1 "WARNING" app.log
マッチした行の前1行、後2行を表示
ファイル検索とカウント
grep -l "TODO" *.js
"TODO"を含むファイル名のみを表示
grep -c "error" log.txt
"error"を含む行数をカウント
grep -r "password" /etc/
ディレクトリを再帰的に検索
🎯 正規表現との組み合わせ
grepの真の力は正規表現との組み合わせで発揮されます。
基本的な正規表現パターン
grep "^Linux" document.txt
行の始まりが"Linux"の行を検索
grep "finished$" log.txt
行の終わりが"finished"の行を検索
grep "^$" file.txt
空行を検索
文字クラスと量詞
grep "[0-9]+" numbers.txt
1つ以上の数字を含む行を検索
grep "colou?r" text.txt
"color"または"colour"を検索(?は0回または1回)
grep -E "error|warning|fatal" log.txt
複数のパターンのいずれかにマッチ(OR検索)
実用的なパターン例
grep -E "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}" access.log
IPアドレスパターンを検索
grep -E "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}" contacts.txt
メールアドレスパターンを検索
grep -E "20[0-9]{2}-[0-1][0-9]-[0-3][0-9]" log.txt
日付パターン(YYYY-MM-DD)を検索
🔄 grepとパイプの組み合わせ
他のコマンドとパイプで繋ぐことで、強力なデータ処理パイプラインを構築できます。
プロセス管理との組み合わせ
ps aux | grep "nginx"
nginxプロセスのみを表示
ps aux | grep -v "grep" | grep "python"
grep自身を除外してpythonプロセスを表示
ログ解析との組み合わせ
tail -f /var/log/app.log | grep --line-buffered "ERROR"
リアルタイムでエラーを監視
cat access.log | grep "404" | wc -l
404エラーの発生回数をカウント
ネットワーク情報との組み合わせ
netstat -an | grep ":80 "
ポート80で待ち受けているプロセスを表示
ifconfig | grep -E "inet [0-9]+"
IPアドレス情報のみを抽出
💡 実用的なgrepテクニック
複数条件の組み合わせ
grep "error" log.txt | grep -v "timeout"
errorを含むがtimeoutは含まない行
grep -E "(error|warning)" log.txt | grep "2025-01-15"
特定日のエラーまたは警告
効率的な検索設定
grep --color=always "pattern" file.txt | less -R
lessで表示する際もカラー表示を維持
GREP_OPTIONS="--color=auto" grep "pattern" file.txt
環境変数でデフォルトオプションを設定
高速化テクニック
LC_ALL=C grep "pattern" large_file.txt
ロケール設定でUTF-8処理を無効化して高速化
grep -F "literal_string" file.txt
固定文字列検索(正規表現処理を無効化)
🚀 上級者向けgrep活用法
複数ファイルでの並列検索
find /var/log -name "*.log" | xargs -P 4 grep "ERROR"
4プロセスで並列検索
🎯 grep究極テクニック:プロフェッショナルレベル
基本をマスターしたら、grepの隠された機能と高度なテクニックでデータ処理を極めましょう。
🌐 環境変数とロケール最適化
大容量ファイル処理では、ロケール設定がパフォーマンスに大きく影響します。
🐌 遅い方法(UTF-8処理)
grep "ERROR" huge_log.txt
文字エンコーディング処理でオーバーヘッド
⚡ 高速化(ASCII処理)
LC_ALL=C grep "ERROR" huge_log.txt
ASCII処理で最大10倍高速化
LC_ALL=C grep --binary-files=without-match "pattern" /var/log/*
バイナリファイルをスキップして高速検索
GREP_OPTIONS="--color=never" LC_ALL=C grep -F "ERROR" *.log
色付けを無効化してさらに高速化
🔗 パイプライン組み合わせマスタリー
複数のgrepを組み合わせて、複雑な条件を効率的に処理できます。
🎯 段階的フィルタリング
grep "ERROR" app.log | grep -v "Timeout" | grep "$(date +%Y-%m-%d)"
ERROR行から今日のタイムアウト以外を抽出
📊 統計情報付き検索
grep -h "ERROR" /var/log/*.log | sort | uniq -c | sort -nr
エラーの種類別出現回数をランキング表示
🕐 時系列分析
grep "ERROR" app.log | grep -o "[0-9]{2}:[0-9]{2}:[0-9]{2}" | cut -c1-2 | sort | uniq -c
エラー発生時間を時間帯別に集計
⚡ 次世代grep:ripgrepとagの活用
従来のgrepより高速で多機能な代替ツールを使いこなしましょう。
🦀 ripgrep(rg)- Rust製高速grep
rg --type js "function" /var/www/
JavaScriptファイルのみを対象に高速検索
rg --json "ERROR" /var/log/ | jq '.data.lines.text'
JSON出力で構造化データとして処理
rg --stats --count "TODO" ./src/
検索統計と件数を同時表示
⚡ ag(The Silver Searcher)
ag --parallel "pattern" /large/directory/
マルチコア並列処理で大容量検索
ag --context=5 --group "ERROR" /var/log/
前後5行表示でグループ化
📈 パフォーマンス比較(1GBファイル検索)
| ツール | 実行時間 | メモリ使用量 | 特徴 |
|---|---|---|---|
| grep | 15.2秒 | 2MB | 標準、安定 |
| LC_ALL=C grep | 8.1秒 | 2MB | 高速化済み |
| ripgrep (rg) | 2.3秒 | 8MB | 最高速、多機能 |
| ag | 4.1秒 | 12MB | 高速、開発者向け |
🧠 複雑なパターンマッチング戦略
複数条件や除外条件を効率的に組み合わせる高度なテクニックです。
🎯 複数キーワードAND条件
grep "ERROR" app.log | grep "database" | grep "timeout"
基本的な方法(3回パイプ)
grep -E "^.*ERROR.*database.*timeout.*$" app.log
1回の正規表現で処理(高速)
🚫 複雑な除外パターン
grep -v -E "(DEBUG|INFO|TRACE)" app.log | grep -v "health_check"
複数レベルでの除外フィルタリング
📅 時間範囲指定検索
grep -E "2024-01-(0[1-9]|[12][0-9]|3[01]) (0[89]|1[0-7]):" app.log
1月1-31日の8-17時台のログを抽出
💾 大容量ファイル処理の極意
数GB〜TBクラスのファイルを効率的に処理する方法です。
🔄 ストリーミング処理
tail -f /var/log/huge.log | grep --line-buffered "ERROR"
リアルタイムでログを監視しながら検索
📦 圧縮ファイル直接検索
zgrep "ERROR" /var/log/app.log.gz
gzip圧縮ファイルを解凍せずに検索
bzgrep "pattern" archive.log.bz2
bzip2ファイルも直接検索可能
⚡ 並列分割処理
split -l 1000000 huge.log chunk_ && grep "ERROR" chunk_* | sort
大容量ファイルを分割して並列処理
🎨 出力カスタマイズとレポート生成
検索結果を見やすく整形し、レポート用に加工するテクニックです。
🌈 カラー出力最適化
GREP_COLORS='ms=1;31:mc=1;31:sl=:cx=:fn=1;32:ln=1;33:bn=1;33:se=' grep --color=always "ERROR" app.log
カスタムカラー設定で視認性向上
📋 構造化出力生成
grep -n "ERROR" *.log | awk -F: '{print $1","$2","$3}' > error_report.csv
エラーレポートをCSV形式で生成
📊 統計レポート自動生成
{
echo "=== ERROR分析レポート $(date) ==="
echo "総エラー数: $(grep -c ERROR app.log)"
echo "ユニークエラー数: $(grep -o 'ERROR.*' app.log | sort -u | wc -l)"
echo "Top 5 エラー:"
grep -o 'ERROR.*' app.log | sort | uniq -c | sort -nr | head -5
}
包括的なエラー分析レポート生成
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操作
列の抽出
awk '{print $1}' employees.csv
1列目(名前)のみを表示
awk '{print $2, $3}' employees.csv
2列目と3列目を表示
awk '{print NR ": " $0}' file.txt
行番号付きで全体を表示
区切り文字の指定
awk -F ',' '{print $1}' data.csv
カンマ区切りファイルの1列目を表示
awk -F ':' '{print $1, $3}' /etc/passwd
コロン区切りのユーザー名とUIDを表示
awk 'BEGIN {FS="\t"} {print $2}' tab_separated.txt
タブ区切りファイルの2列目を表示
条件付き処理
awk '$2 > 25 {print $1, $2}' employees.csv
年齢が25歳より上の人の名前と年齢を表示
awk '$3 == "エンジニア" {print $1}' employees.csv
職業がエンジニアの人の名前を表示
awk 'NF > 3 {print NR, $0}' data.txt
フィールド数が3より多い行を行番号付きで表示
📊 計算・集計処理
awkの強力な機能の一つが数値計算と集計処理です。
基本的な計算
awk '{sum += $3} END {print "合計:", sum}' sales.csv
3列目(売上など)の合計を計算
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
2列目の最大値を求める
グループ別集計
awk '{dept[$3] += $2} END {for (d in dept) print d, dept[d]}' salary.csv
部署別の給与合計を計算
awk '{count[$1]++} END {for (c in count) print c, count[c]}' access.log
IPアドレス別のアクセス回数をカウント
複雑な処理例
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 パターン
ファイル処理の開始前に実行
awk 'BEGIN {print "処理開始", "氏名", "年齢"} {print NR, $1, $2}' data.txt
ヘッダーを出力してからデータを処理
END パターン
ファイル処理の終了後に実行
awk '{count++} END {print "総レコード数:", count}' data.txt
処理後に総レコード数を表示
組み合わせ例
awk 'BEGIN {print "=== 売上レポート ==="} {total+=$3} END {print "総売上:", total, "円"}' sales.txt
レポート形式で売上集計
🚀 高度なawk活用法
📊 複数ファイルの処理
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 '{gsub(/-/, "/", $1); cmd="date -d " $1 " +%w"; cmd | getline weekday; print $0, weekday}' dates.txt
日付から曜日を求めて追加
🥋 awk黒帯レベル:データ処理の極意
基本をマスターしたら、awkの隠された力と高度なプログラミングテクニックを習得しましょう。
🧠 連想配列の完全活用
awkの真の力は連想配列(ハッシュテーブル)にあります。多次元データ処理で威力を発揮します。
📊 多次元集計(地域×月別売上)
awk -F, '
NR>1 {
# sales[地域][月] += 売上
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];
}
# 月計出力
printf "%-12s", "月計";
for (month in total_by_month) printf "%10d", total_by_month[month];
printf "%12d\n", grand_total;
}' sales_data.csv
CSV形式の売上データからクロス集計表を生成
🔍 データ重複検出と統計
awk '
{
# 行全体の出現回数をカウント
count[$0]++;
# 初回出現時の行番号を記録
if (!first_occurrence[$0]) {
first_occurrence[$0] = NR;
}
}
END {
print "=== 重複データ分析レポート ===";
duplicates = 0;
unique_count = 0;
for (line in count) {
if (count[line] > 1) {
printf "重複データ: %s (出現回数: %d, 初回行: %d)\n",
line, count[line], first_occurrence[line];
duplicates++;
} else {
unique_count++;
}
}
printf "\n統計情報:\n";
printf "総行数: %d\n", NR;
printf "ユニーク行数: %d\n", unique_count;
printf "重複パターン数: %d\n", duplicates;
printf "データ重複率: %.2f%%\n", (duplicates * 100.0) / (unique_count + duplicates);
}' data_file.txt
データの重複を検出し、詳細な統計レポートを生成
🔧 ユーザー定義関数とモジュール化
複雑な処理を関数化して再利用し、保守性の高いコードを作成します。
📅 日付処理ライブラリ
awk '
# 日付の妥当性をチェックする関数
function is_valid_date(date_str, parts, year, month, day, days_in_month) {
if (split(date_str, parts, "-") != 3) return 0;
year = parts[1]; month = parts[2]; day = parts[3];
if (year < 1900 || year > 2100) return 0;
if (month < 1 || month > 12) return 0;
# 月ごとの日数チェック(うるう年考慮)
days_in_month = "31,28,31,30,31,30,31,31,30,31,30,31";
split(days_in_month, month_days, ",");
if (month == 2 && is_leap_year(year)) {
max_day = 29;
} else {
max_day = month_days[month];
}
return (day >= 1 && day <= max_day);
}
# うるう年判定関数
function is_leap_year(year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
# 日付差計算関数(簡易版)
function date_diff_days(date1, date2, cmd, result) {
cmd = sprintf("date -d \"%s\" +%%s", date1);
cmd | getline timestamp1; close(cmd);
cmd = sprintf("date -d \"%s\" +%%s", date2);
cmd | getline timestamp2; close(cmd);
return int((timestamp2 - timestamp1) / 86400);
}
# メイン処理
{
if (is_valid_date($1)) {
diff = date_diff_days($1, "'$(date +%Y-%m-%d)'");
printf "%s: %s (%d日%s)\n", $1,
(diff >= 0) ? "未来" : "過去",
(diff < 0) ? -diff : diff,
(diff >= 0) ? "後" : "前";
} else {
printf "%s: 無効な日付形式\n", $1;
}
}' date_list.txt
日付の妥当性チェック、うるう年判定、日付差計算を行う関数ライブラリ
🔢 統計計算ライブラリ
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);
}
# 配列の中央値を計算
function median(arr, count, temp_arr, i, j, tmp) {
# 配列をコピーしてソート
for (i = 1; i <= count; i++) temp_arr[i] = arr[i];
# バブルソート(小さな配列用)
for (i = 1; i <= count; i++) {
for (j = i + 1; j <= count; j++) {
if (temp_arr[i] > temp_arr[j]) {
tmp = temp_arr[i];
temp_arr[i] = temp_arr[j];
temp_arr[j] = tmp;
}
}
}
if (count % 2 == 1) {
return temp_arr[int(count/2) + 1];
} else {
return (temp_arr[count/2] + temp_arr[count/2 + 1]) / 2;
}
}
# データ収集
{
if (NF >= 2 && $2 ~ /^[0-9]+\.?[0-9]*$/) {
values[++count] = $2;
sum += $2;
if (min == "" || $2 < min) min = $2;
if (max == "" || $2 > max) max = $2;
}
}
END {
if (count > 0) {
printf "統計サマリー (n=%d)\n", count;
printf "==================\n";
printf "最小値: %8.2f\n", min;
printf "最大値: %8.2f\n", max;
printf "平均値: %8.2f\n", average(values, count);
printf "中央値: %8.2f\n", median(values, count);
printf "標準偏差: %8.2f\n", stddev(values, count);
printf "合計: %8.2f\n", sum;
}
}' numerical_data.txt
数値データの統計分析(平均、中央値、標準偏差等)を行う関数群
🌊 ストリーム処理とgetline活用
リアルタイムデータ処理や外部コマンド連携で真価を発揮するテクニックです。
📡 リアルタイムログ監視
# tail -f でリアルタイム監視
tail -f /var/log/apache2/access.log | awk '
BEGIN {
# 時間窓設定(5分間)
window_size = 300;
alert_threshold = 100;
}
{
# タイムスタンプから時刻を抽出
if (match($4, /\[([^\]]+)\]/, timestamp)) {
# 現在時刻の取得
"date +%s" | getline current_time;
close("date +%s");
# アクセス記録
access_times[current_time]++;
# 古いデータの削除(5分以上前)
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] %s: High traffic detected - %d requests in last 5 minutes\n",
strftime("%Y-%m-%d %H:%M:%S", current_time), total_access | "cat >&2";
}
# 定期レポート(1分毎)
if (current_time % 60 == 0) {
printf "[INFO] %s: Current window traffic: %d requests\n",
strftime("%Y-%m-%d %H:%M:%S", current_time), total_access;
}
}
}'
Webサーバーログをリアルタイム監視し、高負荷時にアラートを出力
🔄 外部API連携データ処理
awk -F, '
# IP地理情報取得関数
function get_geo_info(ip, cmd, result, location) {
if (ip in geo_cache) return geo_cache[ip];
cmd = sprintf("curl -s \"http://ip-api.com/line/%s?fields=country,regionName,city\"", ip);
cmd | getline result;
close(cmd);
# 結果をキャッシュ
geo_cache[ip] = result;
return result;
}
# メイン処理(アクセスログ解析)
NR > 1 {
ip = $1;
url = $7;
status = $9;
# 地理情報取得(APIレート制限を考慮)
if (++api_calls <= 100) { # 1回の実行で最大100回のAPI呼び出し
geo_info = get_geo_info(ip);
split(geo_info, geo_parts, ",");
country = geo_parts[1];
region = geo_parts[2];
city = geo_parts[3];
# 国別統計
country_stats[country]++;
if (status >= 400) {
country_errors[country]++;
}
}
# URL別統計
url_stats[url]++;
if (status >= 400) {
url_errors[url]++;
}
}
END {
print "=== 地理的アクセス分析 ===";
for (country in country_stats) {
error_rate = (country in country_errors) ?
(country_errors[country] * 100.0 / country_stats[country]) : 0;
printf "%-20s: %6d アクセス (エラー率: %5.1f%%)\n",
country, country_stats[country], error_rate;
}
print "\n=== 問題のあるURL ===";
for (url in url_stats) {
if (url in url_errors && url_errors[url] > 10) {
error_rate = url_errors[url] * 100.0 / url_stats[url];
printf "%-50s: エラー %3d/%3d (%.1f%%)\n",
url, url_errors[url], url_stats[url], error_rate;
}
}
}' access_log.csv
アクセスログにIP地理情報を付加し、国別エラー率分析を実行
🚀 パフォーマンス最適化とメモリ管理
大容量データ処理で速度とメモリ効率を最大化するテクニックです。
⚡ 高速な文字列処理
🐌 遅い方法
# 文字列結合を繰り返し(遅い)
awk '{
result = "";
for (i = 1; i <= NF; i++) {
result = result $i " "; # 毎回新しい文字列を作成
}
print result;
}'
⚡ 高速な方法
# 配列を使った効率的な文字列処理
awk '{
for (i = 1; i <= NF; i++) {
words[i] = $i; # 配列に格納
}
# 一度に結合して出力
for (i = 1; i <= NF; i++) {
printf "%s%s", words[i], (i < NF) ? " " : "\n";
}
# 配列をクリア(メモリ節約)
delete words;
}'
💾 メモリ効率的な大容量ファイル処理
awk '
BEGIN {
# 処理済みレコード数のカウンタ
processed = 0;
batch_size = 10000;
}
{
# レコード処理
process_record($0);
processed++;
# バッチ処理(メモリ使用量制御)
if (processed % batch_size == 0) {
# 定期的に不要なデータを削除
cleanup_memory();
# 進捗報告
printf "処理中: %d レコード完了 (%.1f MB processed)\n",
processed, processed * length($0) / 1024 / 1024 > "/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];
}
# ガベージコレクション的処理
system("echo 3 > /proc/sys/vm/drop_caches 2>/dev/null || true");
}
END {
# 最終結果出力
for (key in summary) {
printf "%s: %d\n", key, summary[key];
}
printf "総処理レコード数: %d\n", processed > "/dev/stderr";
}' huge_data_file.csv
大容量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 "================";
printf "スケール: 1文字 = %.0f万円\n\n", scale / 10000;
# 売上順にソート用の配列準備
n = 0;
for (person in sales) {
sorted_sales[++n] = sales[person];
person_by_sales[sales[person]] = person;
}
# バブルソート(売上降順)
for (i = 1; i <= n; i++) {
for (j = i + 1; j <= n; j++) {
if (sorted_sales[i] < sorted_sales[j]) {
tmp = sorted_sales[i];
sorted_sales[i] = sorted_sales[j];
sorted_sales[j] = tmp;
}
}
}
# チャート出力
for (i = 1; i <= n; i++) {
current_sales = sorted_sales[i];
person = person_by_sales[current_sales];
bar_length = int(current_sales / scale);
printf "%-10s |", person;
for (j = 1; j <= bar_length; j++) printf "█";
printf " %d万円\n", current_sales / 10000;
}
print "";
printf "合計売上: %d万円\n", total_sales / 10000;
printf "平均売上: %.1f万円\n", (total_sales / n) / 10000;
}' sales_report.csv
売上データからアスキーアートの棒グラフを生成