bashでCSVを扱う方法まとめ|読み込み・ループ・配列処理の実践例

実務レシピ

「日次で出力される売上CSVから特定列だけを取り出して集計したい」「CSV形式のサーバーリストを読み込んで、全台に一括でヘルスチェックや設定変更を実行したい」――システム運用やデータ移行、定型バッチ処理の現場において、CSV・テキストデータの加工自動化はエンジニアにとって必須のスキルです。

bashには、標準のwhile read構文をはじめ、高速な一括配列化を行うmapfile、特定列の高速切り出しを行うcut、そして高度な条件分岐・集計が可能なawksedとのパイプ連携など、多彩な手法が用意されています。本記事では、「CSV・テキストデータ加工」クラスターの総合ピラー(親記事)として、基本の読み込みから配列操作、特定列抽出、実務ユースケース、ダブルクォート混在時のエスケープ落とし穴までを網羅して解説します。

1. CSV読み込み・ループ処理の基本と安全な構文

bashでCSVファイルを1行ずつ読み込んで処理する場合、もっとも基本かつ堅牢なのがwhile IFS=, read -r構文です。環境変数IFS(Internal Field Separator:内部フィールド区切り文字)にカンマを指定することで、各列の値を別々の変数へ直接代入できます。

#!/bin/bash
# --------------------------------------------------
# 基本のCSV読み込みと変数展開
# --------------------------------------------------

# サンプルCSV(users.csv)の想定構造:
# ユーザー名,メールアドレス,所属部署
# sato,sato@example.com,開発部
# suzuki,suzuki@example.com,インフラ部

csv_file="users.csv"

# IFSをカンマに設定し、read -r で安全に読み込み
while IFS=, read -r username email department
do
  # 各フィールドを展開して処理(空行対策も含む)
  if [[ -n "$username" ]]; then
    echo "アカウント: $username | 宛先: $email | 部署: $department"
  fi
done < "$csv_file"

この構文の重要なポイントは以下の3点です:

  • IFS=, をコマンド直前に付与: スクリプト全体のIFSを書き換えず、readコマンドの実行時のみカンマ区切りを適用するため、後続の処理に影響を与えません。
  • -r オプションの必須指定: バックスラッシュ()をエスケープ文字として解釈せず、データそのままの文字列として読み込みます。パス名や特殊文字が含まれるデータで必須です。
  • 変数のダブルクォート囲み("$username": 空白を含む値であっても単語分割(Word Splitting)を防ぎ、安全に扱えます。

より詳しいIFSのスコープ制御やwhile readの応用テクニックは、以下のサテライト解説記事も参考にしてください。

2. 配列への格納(動的参照とmapfileによる一括処理)

CSVの列数が可変の場合や、行全体を一括でメモリに展開して高速走査したい場合は、配列(Array)を活用します。

① 1行を配列に分割して動的アクセス(read -a)

read -a 配列名 を指定すると、カンマで区切られた各フィールドが自動的に0始まりのインデックス配列に格納されます。

#!/bin/bash
# read -a を使った動的列アクセス

while IFS=, read -r -a fields
do
  # 要素数の取得
  col_count="${#fields[@]}"
  
  # インデックス指定で参照
  echo "列数: $col_count | 1列目: ${fields[0]} | 最終列: ${fields[$((col_count - 1))]}"
done < data.csv

② mapfile(readarray)による一括配列化

Bash 4.0以降で利用可能な組み込みコマンド mapfile(別名 readarray)を使うと、外部ファイルを1行ごとに配列要素として一括読み込みできます。サブシェルを起動しないため、数千行〜数万行のデータでも高速に処理できます。

#!/bin/bash
# mapfileでCSVを行単位の配列に一括格納

# -t オプションで各行末尾の改行コードを自動削除
mapfile -t lines < server_list.csv

echo "総行数: ${#lines[@]}"

# 配列をループ処理
for line in "${lines[@]}"
do
  # 必要に応じてカンマで分割
  IFS=, read -r host ip role <<< "$line"
  echo "ホスト名: $host ($ip) - 役割: $role"
done

3. 特定列の抽出とデータフィルタリング(cut・大量データ抽出)

「CSV全体のループは不要で、特定の2列目と4列目だけを抜き出したい」「不要なヘッダーを除去して集計用の中間ファイルを作りたい」という場合は、cutコマンドが最速の選択肢です。

コマンド・構文主な用途実行例
cut -d, -f22列目のみを抽出cut -d, -f2 data.csv
cut -d, -f1,31列目と3列目を抽出cut -d, -f1,3 data.csv
cut -d, -f2-2列目以降の全列を抽出cut -d, -f2- data.csv
cut -f1,2TSV(タブ区切り)の列抽出cut -f1,2 data.tsv
#!/bin/bash
# cutコマンドとパイプを組み合わせた高速データ抽出

# 大規模ログCSVから「アクセス日時(1列目)」と「IPアドレス(3列目)」を抽出してユニーク集計
cut -d, -f1,3 access_log.csv | sort | uniq -c | sort -nr | head -n 10

cutは処理速度が極めて高速ですが、「ダブルクォート内のカンマ」を無視できず一律に分割してしまう制限があります。複雑な抽出条件やクォート処理が必要な場合は、後述のawkや専用抽出手法を併用します。

4. 実務連携:awk・sed・一括置換との組み合わせ

単なるループ処理を超えて、「条件に応じた数値計算」「特定カラムの文字置換」「複数ファイルの一括編集」を行うには、awksed の連携が威力を発揮します。

① awkによる条件付き集計・列加工

# 金額列(3列目)が10,000円以上の行のみ抽出し、消費税込み価格を出力
awk -F, '$3 >= 10000 { printf "商品: %-15s 金額(税込): %dn", $1, $3 * 1.1 }' sales.csv

# 部署別(2列目)の合計売上(3列目)を集計
awk -F, 'NR>1 { sum[$2] += $3 } END { for (d in sum) print d, sum[d] }' sales.csv

② sedによるCSV内文字列の一括置換とファイル更新

# CSV内のドメイン名を一括更新(直接上書き保存)
sed -i 's/@old-domain.com/@new-company.jp/g' users.csv

# 複数の一括CSVファイルに対してまとめて置換を実行
find /var/data/csv/ -name "*.csv" -exec sed -i 's/STATUS_PENDING/STATUS_ACTIVE/g' {} +

5. 実務で役立つ頻出ユースケース集(コピペ実装例)

① CSVからユーザーアカウントを一括作成・権限設定

社内システムの移行や初期構築時に役立つ、ユーザー追加スクリプトのテンプレートです。ヘッダーをスキップし、既存ユーザーの重複チェックを行いながら安全に処理します。

#!/bin/bash
set -euo pipefail

csv_file="new_users.csv"

# 1行目(ヘッダー)を飛ばして2行目から読み込み
tail -n +2 "$csv_file" | while IFS=, read -r username group initial_pass
do
  # 空白行はスキップ
  [[ -z "$username" ]] && continue

  if id "$username" &>/dev/null; then
    echo "[SKIP] ユーザー $username は既に存在します。"
  else
    useradd -m -g "$group" -s /bin/bash "$username"
    echo "${username}:${initial_pass}" | chpasswd
    echo "[OK] ユーザー $username を作成しました(グループ: $group)"
  fi
done

② サーバーリストCSVに基づくSSH並行ヘルスチェック

対象サーバーのホスト名とIPアドレスが記載されたCSVを順次読み込み、SSH経由でディスク使用率やロードアベレージを点検します。

#!/bin/bash
# サーバー一覧CSV(ホスト名,IPアドレス,環境)から稼働確認
csv_file="servers.csv"

while IFS=, read -r hostname ip_addr env_type
do
  # ヘッダー行判定
  [[ "$hostname" == "hostname" ]] && continue

  echo "=== Checking: $hostname ($ip_addr) [$env_type] ==="
  
  # 接続タイムアウト3秒でuptimeを取得
  if ssh -o ConnectTimeout=3 -o BatchMode=yes "admin@${ip_addr}" "uptime" 2>/dev/null; then
    echo "[$hostname] 正常稼働中"
  else
    echo "[$hostname] 警告: 接続できませんでした" >&2
  fi
done < "$csv_file"

③ Excelファイル(xlsx)からCSVへの自動変換パイプライン

実務ではクライアントや管理部門からExcel形式でデータが提供されることが多々あります。Linuxサーバー上ではssconvert(Gnumericツール)やlibreofficeのCLIを使ってヘッドレス変換してからbashスクリプトへパイプで渡すのが定石です。

# ssconvert を使って高速にCSV変換
ssconvert input_data.xlsx output_data.csv

# ヘッドレスLibreOfficeによるバッチ変換
libreoffice --headless --convert-to csv --outdir /tmp/ input_data.xlsx

# 変換後のCSVを即座にパイプでbash処理
tail -n +2 /tmp/input_data.csv | while IFS=, read -r col1 col2 col3
do
  echo "処理中: $col1 -> $col2"
done

6. 実務での注意点・落とし穴とトラブルシューティング

① ダブルクォート囲み・カンマ混在データ(”Tokyo, Japan”,100)の罠

標準のIFS=, read構文は「単純なカンマ区切り」のみを認識するため、"東京都, 新宿区",160-0022 のようにダブルクォート内にカンマを含むCSVを渡すと、誤って列が分割されてしまいます。

  • 対処法1(awk FPAT): GNU awkの FPAT を使い、awk -v FPAT='([^,]+)|("[^"]+")' でクォート内を保護して処理する。
  • 対処法2(専用ツール活用): CSV専用CLIツール csvkitcsvcut, csvgrep)や Pythonの csv モジュールワンライナーに委譲する。
# Pythonのワンライナーでクォート付きCSVを安全にTSV変換してbashへ渡す例
python3 -c '
import csv, sys
reader = csv.reader(sys.stdin)
for row in reader:
    print("t".join(row))
' < quoted_data.csv | while IFS=$'t' read -r col1 col2 col3
do
  echo "安全取得: $col1 | $col2 | $col3"
done

② 改行コード(CRLF vs LF)の混入

Windows環境で作成・編集されたCSVファイルには行末に rn(CRLF)が含まれています。このままbashで読み込むと、最終列の変数の末尾に不可視の r(キャリッジリターン)が残り、文字列比較の失敗や出力崩れの原因になります。

# dos2unix で事前に改行コードをLFに変換
dos2unix data.csv

# またはスクリプト内で tr を通してCRを除去
tr -d 'r' < data.csv | while IFS=, read -r col1 col2
do
  # 正常に比較・処理可能
  if [[ "$col2" == "active" ]]; then
    echo "$col1 is active"
  fi
done

③ ヘッダー行の安全なスキップ手法

1行目のヘッダー(列名)を処理対象外にするには、主に2つの方法があります:

  • パイプライン方式: tail -n +2 file.csv | while IFS=, read -r ...(外部プロセスで2行目以降に絞る)
  • ループ内空打ち方式: ループの直前に read -r header を1回実行してファイルポインタを進める
{
  # 1行目(ヘッダー)を読み捨て
  read -r header
  # 2行目以降を通常処理
  while IFS=, read -r col1 col2
  do
    echo "$col1: $col2"
  done
} < data.csv

7. まとめ:CSV・テキスト加工手法の早見表

加工手法・ツール得意なユースケースメリット注意点・デメリット
while IFS=, read -r1行ごとの逐次処理・外部コマンド実行Bash標準・依存なし・シンプル行数が多いと速度低下、クォート内カンマに非対応
mapfile (readarray)全行の一括配列化・高速ループサブシェル不要で高速・配列操作が容易巨大ファイルではメモリを消費(Bash 4+必須)
cut コマンド特定列の高速切り出し最速の処理速度・パイプ連携に最適カンマ以外の柔軟な条件判定やクォート処理は不可
awk コマンド条件フィルタリング・集計・計算柔軟なプログラミング構文・高速文法がスクリプト言語的で習熟が必要
sed コマンド文字列の一括置換・行削除・ファイル更新正規表現を用いた高速テキスト一括編集列単位の高度な計算には不向き

日々の定型業務では、「シンプルな列抽出は cut / awk、1行ずつの自動化バッチは while read、全行メモリ走査は mapfile」 と使い分けるのが最も効率的です。それぞれの詳細なコマンドオプションや実務レシピは、本記事で紹介した各サテライト記事もぜひブックマークして活用してください。

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント