HOWTO · Linux

Bash でファイルを一行ずつ読み込む方法

ファイルを一行ずつ読み込むには、Bash の read コマンドを使用します。

このページの内容

Bashでは、while ループ内で read コマンドを使うと、テキストファイルを1行ずつ処理できます。入力をストリームとして扱うため、ファイル全体をメモリに保存する必要はありません。先頭と末尾の空白を保つには IFS= を指定し、バックスラッシュをそのまま扱うには -r を使います。また、値を展開するときは必ず "$line" のように引用符で囲みます。

while ループでファイルを1行ずつ読む

信頼できる方法は、入力リダイレクトを使ってファイルをループに読み込むことです。|| [[ -n $line ]] という条件を付けると、ファイル末尾に改行がなくても最後の行を処理できます。ループはファイルをストリームとして扱い、加算中の合計は現在のシェルに保持します。

次の完全な例では、数値を含む入力ファイルを自分で作成し、各値を表示して、1、5、6、7、8、10の6つを合計します。合計は37です。コードと出力にあるラベルや表示文は英語のままです。表示される数値は各行の値を示し、最後の表示文は合計が37であることを伝えています。

#!/usr/bin/env bash
set -u

file=$(mktemp)
trap 'rm -f "$file"' EXIT
printf '%s\n' 1 5 6 7 8 10 > "$file"
sum=0
printf 'The numbers in the file are:\n'
while IFS= read -r line || [[ -n $line ]]; do
  printf '%s\n' "$line"
  sum=$((sum + line))
done < "$file"
printf 'The sum of the numbers in the file is:%s\n' "$sum"
The numbers in the file are:
1
5
6
7
8
10
The sum of the numbers in the file is:37

入力リダイレクトを使うとループが現在のシェルで実行されるため、加算中の合計などの変更をループ終了後も利用できます。$line のように引用符を付けずに展開するのは避けてください。単語分割やパス名展開によって値が変わることがあります。

各行をフィールドに分割する

区切り文字でレコードを分割するには、まず各行をそのまま読み込み、その後でフィールドを割り当てます。区切り文字を指定した read では、最後の変数に残りのフィールドが入ります。この例では、Rohit-10、Harish-30、Manish-50、Kapil-10、Anish-20の5件すべてを作成して処理します。末尾に改行がないファイルでも、最後の行が空でなければ最終行の条件によって処理されます。

#!/usr/bin/env bash
set -u

file=$(mktemp)
trap 'rm -f "$file"' EXIT
printf '%s\n' 'Rohit-10' 'Harish-30' 'Manish-50' 'Kapil-10' 'Anish-20' > "$file"
while IFS= read -r line || [[ -n $line ]]; do
  IFS=- read -r name earnings <<< "$line"
  printf '%s has made earnings of %s pounds today!\n' "$name" "$earnings"
done < "$file"
Rohit has made earnings of 10 pounds today!
Harish has made earnings of 30 pounds today!
Manish has made earnings of 50 pounds today!
Kapil has made earnings of 10 pounds today!
Anish has made earnings of 20 pounds today!

ここでは IFS=- と指定し、フィールド割り当て時の区切り文字をハイフンにしています。変数を2つ使うと、name には最初のハイフンより前の文字列が入り、earnings には残りが入ります。表示時には変数を引用符で囲み、内容がそれぞれ1つの値として渡されるようにします。

Bashでファイルを読む別の方法

各行を読み込んだそばから処理したい場合や、ファイルが利用可能なメモリより大きい可能性がある場合は、while IFS= read -r line のパターンを使います。通常のファイルなら、そのままループにリダイレクトしてください。cat をプロセス置換経由で実行しても利点はありません。ファイルではなく、コマンドが生成する出力をループで読みたい場合には、プロセス置換が役立つことがあります。

使用している Bash 環境で mapfile が利用できる場合は、たとえば mapfile -t lines < "$file" のようにして、ファイルの各行を配列に読み込めます。後続の処理で行をインデックス指定したり、繰り返し参照したりする必要があり、ファイル全体がメモリに収まる場合に適しています。-t オプションは各行末の改行を取り除きます。利用できるかどうかは Bash 環境によって異なるため、より幅広い環境で使える方法が必要なら、ストリーム処理のループを使ってください。