while readコマンドでシェルスクリプトのファイルを1行ずつ処理する方法|IFS設定とCSV・ログ解析の実践例も

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Linuxtips, シェルスクリプト > while readコマンドでシェルスクリプトのファイルを1行ずつ処理する方法|IFS設定とCSV・ログ解析の実践例も
「シェルスクリプトでファイルの中身を1行ずつ処理したいのに、うまくいかない」
そんなつまずきを抱えたまま、forループやcatを組み合わせて試行錯誤していませんか。

実は、Bashには while read というファイルを1行ずつ読み込むための専用の書き方があります。
ログファイルの解析、CSVの一括処理、サーバー一覧への順次SSH接続など、現場で繰り返し使う場面がある実務必須の構文です。

この記事では、while read の基本構文から、IFSによる区切り文字のカスタマイズ、CSVやTSVの処理、
よくあるトラブルの対処法まで、RHEL 9.4 / Ubuntu 24.04 LTS の動作確認済みコマンドで解説します。

この記事のポイント

・ while read line でファイルを1行ずつ確実に処理できる
・ IFS=","で区切り文字を変えればCSV・TSVにも対応できる
・ while readはfor+catより安全で空白・改行を壊さない
・ 末尾改行なし・バックスラッシュ・末尾スペースに注意が必要


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

while readとは何か|forループと何が違うのか

シェルスクリプトでファイルを処理するとき、最初に思いつくのが for ループです。
しかし、for line in $(cat file) という書き方には重大な落とし穴があります。

forループの3つの問題点:
・ スペースや改行を含む行が途中で分割される(IFSの影響)
・ ワイルドカード(*)がファイル名展開される場合がある
・ 大きなファイルをcat(コマンド置換)で一括読み込みするためメモリを消費する

一方、while read は標準入力を1行ずつ読み込むため、これらの問題が発生しません。
現場のシェルスクリプトでは while read が圧倒的に多用されています。

forループとwhile readの比較:
比較項目 for line in $(cat file) while read -r line
スペース含む行 分割されてしまう 正しく1行として処理できる
ワイルドカード展開 展開される(危険) 展開されない(安全)
大きなファイル 一括読込でメモリを消費する 1行ずつストリーム処理
バックスラッシュ 展開されてしまう read -r付きで保持できる
推奨度 非推奨 推奨(現場の標準)

readコマンドの主要オプション一覧

while read で使える read コマンドの主要オプションをまとめました。

オプション 説明 使用例
-r(バックスラッシュ無効化) バックスラッシュをエスケープとして解釈しない while read -r line; do
-d(区切り文字変更) 行末の区切り文字を変更する while read -r -d '' line; do
-n(読込文字数制限) 指定した文字数だけ読み込む read -r -n 1 char
-t(タイムアウト) タイムアウトを設定する read -r -t 5 line
-s(非表示入力) 入力を表示しない(パスワード入力等) read -r -s password
-a(配列代入) 入力を配列に代入する read -r -a myarray

while readの基本構文

1. ファイルを1行ずつ読み込む

最もシンプルな書き方です。

# 基本構文 while read line; do echo "$line" done < /path/to/file.txt

動作確認例(RHEL 9.4):

# テスト用ファイルを作成する $ cat /tmp/servers.txt web01.example.com web02.example.com db01.example.com # while readで1行ずつ処理する $ while read line; do > echo "接続先: $line" > done < /tmp/servers.txt 接続先: web01.example.com 接続先: web02.example.com 接続先: db01.example.com

ポイントは末尾の < ファイル名 です。
これはリダイレクト(標準入力の切り替え)で、ファイルの内容をwhileループの入力として渡しています。

2. readのオプション|バックスラッシュ展開を防ぐ -r

read にはよく一緒に使われる重要なオプションがあります。

# -r オプション: バックスラッシュを文字として扱う(展開しない) while read -r line; do echo "$line" done < file.txt

-r を付けない場合、ファイル内の \n\t がエスケープシーケンスとして解釈されることがあります。
パスやログ解析など実務では 基本的に -r を付ける ことを習慣にしてください。

3. 変数を複数指定して列ごとに取り出す

read は複数の変数を指定でき、空白で区切られた値を列ごとに受け取れます。

# テスト用ファイル(ユーザー名とIPアドレスの対応表) $ cat /tmp/user-ip.txt alice 192.168.1.10 bob 192.168.1.20 carol 192.168.1.30 # 変数を2つ指定して列ごとに受け取る $ while read -r username ipaddr; do > echo "ユーザー: $username, IP: $ipaddr" > done < /tmp/user-ip.txt ユーザー: alice, IP: 192.168.1.10 ユーザー: bob, IP: 192.168.1.20 ユーザー: carol, IP: 192.168.1.30

列数が変数の数より多い場合、最後の変数に残りの列が全てまとめて入ります。
これを意図的に利用して最初の1列だけ取り出し、残りは捨てる書き方もよく使われます。

IFSで区切り文字を変えてCSV・TSVを処理する

1. IFSとは何か

IFS(Internal Field Separator)はシェルが単語の区切りとして使う文字を定義する変数です。
デフォルトは「スペース・タブ・改行」ですが、while read の前に変更することで
CSVやTSVなど任意の区切り文字に対応できます。

2. CSVファイルを処理する

# CSVファイルの例 $ cat /tmp/users.csv alice,30,東京 bob,25,大阪 carol,35,名古屋 # IFS="," を設定してCSVを処理する $ while IFS="," read -r name age city; do > echo "名前:$name 年齢:$age 都市:$city" > done < /tmp/users.csv 名前:alice 年齢:30 都市:東京 名前:bob 年齢:25 都市:大阪 名前:carol 年齢:35 都市:名古屋

IFSは while IFS="," read -r ... のように while read の直前で設定するのが定石です。
この書き方にすることでIFSの変更がループ内にだけ影響し、スクリプト全体への副作用を防げます。

3. TSVファイル(タブ区切り)を処理する

# TSVファイルを処理する(IFSにタブを指定) while IFS=$'\t' read -r col1 col2 col3; do echo "$col1 / $col2 / $col3" done < /tmp/data.tsv

タブはシングルクォート内で \t と書けますが、$'\t' の形式(ANSI-C Quoting)を使うと確実です。

実務で使えるwhile readの応用パターン

1. サーバー一覧に順次SSHで接続してコマンドを実行する

現場でよく使うパターンです。サーバーリストを読み込んで1台ずつ操作します。

#!/bin/bash # server-check.sh: サーバー一覧に接続してdiskを確認する SERVERS="/etc/server-list.txt" while read -r server; do echo "=== $server ===" ssh -n -o ConnectTimeout=5 "$server" "df -h /" done < "$SERVERS"

ConnectTimeout=5 で接続タイムアウトを設定することで、応答しないサーバーで止まらないようにしています。
-n オプションはSSHが標準入力を奪わないようにするための設定で、while readと組み合わせる場合は必須です。

2. ログファイルから特定パターンの行を抽出して集計する

#!/bin/bash # log-count.sh: /var/log/secure からSSH失敗ログをIPごとに集計する declare -A fail_count while read -r line; do if echo "$line" | grep -q "Failed password"; then # IPアドレスを抽出する ip=$(echo "$line" | awk '{print $NF}') ((fail_count["$ip"]++)) fi done < /var/log/secure # 集計結果を表示する echo "--- SSH失敗 IPアドレス集計 ---" for ip in "${!fail_count[@]}"; do echo "${fail_count[$ip]}回 $ip" done | sort -rn

実際のサーバー(192.168.10.5 / RHEL 9.4)での実行例:

--- SSH失敗 IPアドレス集計 --- 47回 203.0.113.45 23回 198.51.100.12 8回 192.0.2.88

declare -A はBashの連想配列(ハッシュ)です。IPアドレスをキーにして失敗回数をカウントしています。
このパターンはfail2banのルール調整前の「どのIPが多いか」確認に実際に使っています。

3. CSVからユーザーを一括作成する

#!/bin/bash # create-users.sh: CSVからユーザーを一括作成する # フォーマット: username,uid,group,comment while IFS="," read -r username uid group comment; do # ヘッダー行をスキップする [[ "$username" == "username" ]] && continue useradd -u "$uid" -g "$group" -c "$comment" "$username" echo "作成完了: $username (uid=$uid)" done < /tmp/new-users.csv

[[ ... ]] && continue でヘッダー行をスキップしています。
CSVの1行目がカラム名の場合に有効な定型パターンです。

4. パイプとプロセス置換でコマンド出力を処理する

ファイルだけでなく、コマンドの出力を while read に渡すことも可能です。

# パイプ接続(変数がサブシェル内で消える問題あり) ps aux | grep httpd | grep -v grep | while read -r user pid cpu mem rest; do echo "PID:$pid CPU:$cpu% MEM:$mem% USER:$user" done # プロセス置換を使う書き方(サブシェル問題を回避する) while read -r user pid cpu mem rest; do echo "PID:$pid CPU:$cpu% MEM:$mem% USER:$user" done < <(ps aux | grep httpd | grep -v grep)

注意: パイプ(|)でwhile readに接続すると、ループ内の変数代入がサブシェルで実行されるため
ループ終了後に変数の値が失われます。これを避けるには <<(...) のプロセス置換を使います。

トラブルシュート|よくある問題と対処法

1. 「末尾の行が処理されない」問題

ファイルの最終行に改行がない場合、while read がその行を読み飛ばすことがあります。

# 末尾改行なしのファイルでも確実に処理する書き方 while read -r line || [[ -n "$line" ]]; do echo "$line" done < file.txt

|| [[ -n "$line" ]] を追加することで、readがEOF(ファイル終端)で失敗したときでも
変数 $line に値が残っていれば処理を続けます。本番スクリプトでは常にこの書き方を推奨します。

2. 「行頭・行末のスペースが消える」問題

デフォルトのIFSにスペースが含まれているため、行頭と行末のスペースがトリムされます。

# IFSを空にして行頭・行末のスペースを保持する while IFS= read -r line; do echo "$line" done < file.txt

IFS=(空文字)にすることでスペースのトリムを防ぎ、行の内容を厳密に保持できます。
インデントを含む設定ファイルの処理などで有効です。

3. 「ループ内のSSHが1回で止まる」問題

sshなどの対話コマンドがwhile readの標準入力を奪ってしまい、ループが1回で終わることがあります。

# 解決策1: sshに -n オプションを付けてstdinを/dev/nullにする while read -r server; do ssh -n "$server" "uptime" done < servers.txt # 解決策2: ファイルディスクリプタ3番を使ってstdinと分離する exec 3< servers.txt while read -r server <&3; do ssh "$server" "uptime" done exec 3<&-

ssh -n/dev/null からstdinを読み込む指定です。
ファイルディスクリプタを使う2つ目の方法はより汎用性が高く、rsyncやscpなど他のコマンドにも対応できます。

4. 「バックスラッシュが消える」問題

# NGな書き方(-rなし) while read line; do echo "$line" done < file.txt # OKな書き方(-r付き) while read -r line; do echo "$line" done < file.txt

-r なしの場合、行末の \ を「継続行の記号」として解釈するため次の行と結合されます。
ファイルパスやURLを処理するスクリプトでは特に問題が起きやすいため、-r は必ず付けてください。

本番スクリプト安全化チェックリスト

本番環境のシェルスクリプトでwhile readを使う前に、以下の項目を確認してください。

-r オプション: 付いているか(バックスラッシュの意図しない展開を防ぐ)
IFSの設定: デフォルトのままでいいか(スペース保持が必要なら IFS= にする)
末尾改行対策: || [[ -n "$line" ]] を付けているか
SSH・対話コマンド: -n オプションまたはFD分離をしているか
変数のクォート: "$line" のようにダブルクォートで囲んでいるか(スペース含む値の分割防止)
ファイル存在チェック: ループ前に [ -f "$FILE" ] || exit 1 を書いているか

よくある質問(FAQ)

Q. forループとwhile readはどちらを使うべきですか?
A. ファイルを1行ずつ処理する場合は while read を選んでください。スペースや改行を含む行を安全に扱えます。
for は配列の要素や数値のループに向いています。

Q. while readは大きなファイルでも使えますか?
A. はい、使えます。while read は1行ずつストリーム処理するため、メモリ使用量は行の長さに比例するだけで
ファイルサイズに依存しません。数十GBのログファイルにも問題なく使用できます。

Q. while readで読んだ変数がループ外で使えません。
A. パイプ(|)でwhile readに接続している場合、ループがサブシェルで実行されるため変数がループ外に出ません。
< file によるリダイレクト、または <<(command) のプロセス置換に変更してください。

Q. while readでCSVの中にカンマが含まれるフィールドを処理できますか?
A. IFS="," だけでは "field,with,comma" のような引用符付きフィールドを正しく処理できません。
Pythonやawkなど、RFC 4180準拠のCSVパーサーを使うことを推奨します。

本記事のまとめ

Linux ポート確認の全コマンドでも紹介しているように、シェルスクリプトはLinux運用の中心にあります。
while read を使いこなせると、ポート確認・ログ解析・設定変更など幅広い作業を自動化できます。

また、ls コマンドの基本オプションと組み合わせることで、ディレクトリ内のファイルをリスト化してwhile readで処理するパターンも作れます。

やりたいこと コマンド・書き方
ファイルを1行ずつ処理する while read -r line; do ... done < file.txt
CSVを列ごとに処理する while IFS="," read -r col1 col2; do ... done < file.csv
スペースを保持して処理する while IFS= read -r line; do ... done < file.txt
末尾改行なしファイルを処理する while read -r line || [[ -n "$line" ]]; do ... done
コマンド出力をループで処理する while read -r line; do ... done < <(command)
SSHをwhileで使う(stdin分離) while read -r server; do ssh -n "$server" "cmd"; done < servers.txt

シェルスクリプトを書いても、毎回ネット検索に頼っていませんか?

while readひとつとっても、IFSの落とし穴やSSHとの組み合わせを知らなければ、本番環境での障害対応で判断を誤ります。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として15年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。