ユーニックス総合研究所

  • home
  • archives
  • bash-while

Bashのwhile文の書き方: ループ文で繰り返し処理を行う

  • 作成日: 2020-08-22
  • 更新日: 2023-12-24
  • カテゴリ: Bash

Bashのwhile文の書き方

Bash(バッシュ)にはループ文というループ処理を書ける文が複数あります。
そのループ文の1つにwhile(ホワイル)文があります。

このwhile文は繰り返し処理をしたい時に使うことが出来ます。
たとえばwhile文は↓のように書きます。

i=0  
while [ $i -ne 4 ]; do  
    echo $i  
    ((i++))  
done  
# 0  
# 1  
# 2  
# 3  

この記事ではBashのwhile文について具体的に↓を見ていきます。

  • ループ文とは?
  • for文とwhile文の違い
  • while文の構造
  • 判定部分の式の正体
  • ファイルの内容を1行ずつ読み込む
  • 無限ループ
  • 使用例
  • 問題

ループ文とは?

そもそもループ文とはいったいなんなのか? という話です。
プログラミングにおけるループ文とは、繰り返し処理を行うための文です。
繰り返し処理とは、特定の処理を繰り返し行うということを言います。

🦝 < そのまんまですね

たとえば↓のような処理があったとします。

処理1  
処理2  

これらの処理、処理1と処理2を繰り返すとどうなるのかと言うと、

処理1  
処理2  
処理1  
処理2  
処理1  
処理2  
...  

↑のように処理1と処理2のまとまりが繰り返し行われるようになります。
この繰り返しには回数を設けることも可能です。
つまり、10回繰り返すとか、100回繰り返すとか、あるいは無限に繰り返すとか、そういった回数を指定することが可能だということです。

プログラミングにおけるループ文は、プログラムと言えばこれ! というほどメジャーな機能です。
代表的なループ文にはfor(フォー)文やwhile(ホワイル)文などがあります。
今回の記事で紹介するのはこの内のwhile文のほうです。

for文とwhile文の違い

ループだけに繰り返しになりますが、ループ文にはfor文とwhile文があります。
これらの2つのループ文の違いについて解説します。

まずこれら2つのループ文の役割ですが、基本は同じです。
この2つのループ文の目的は、特定の処理を繰り返すことです。
なぜ2つのループ文に分かれているのかと言うと、これら2つは文の構造が違います。

for文のほうの構造は「初期化・判定・処理・更新」に分かれています。
いっぽうwhile文のほうの構造は「判定・処理」に分かれています。
つまりwhile文のほうがfor文よりシンプルなんですね。

while文のほうがシンプルなだけあってコーディング量もwhile文のほうが少ないです。
そのためプログラミングのシーンによってこれらの2つのループ分をかき分けることでコーディング量を減らすことができます。
コーディング量はプログラミングにおける「疲労の度合い」と直結する重要な開発者にとっての関心事です。
while文はこの労力を減らすことができるという点で価値があります。

また、シンプルなだけあってwhile文はfor文に比べるとわかりやすい構造になっているため、学習コストもfor文と比べると少なくなっています。

while文の構造

while文の構造について解説します。
while文は↓のような構造になっています。

while 判定; do  
    処理  
done  

まずキーワードとして「while」を書き、そのあとに判定を行う式を書きます。
セミコロン(;)のあとにキーワード「do」を書き、その後に繰り返す処理を書きます。
繰り返し処理を書き終わったら最後にwhile文の終端としてキーワード「done」を書きます。

ちなみにセミコロン(;)を付けない場合は↓のようにも書けます。

while 判定  
do  
    処理  
done  

while文は「判定」の式の結果がtrueの間、ループを繰り返します。
判定がfalseになったらその時点でループを終了します。

「判定」部分には式を書くことができます。
式とはたとえば↓のような式です。

i=0  
while [ $i -eq 0 ]; do  
    echo "0です。"  
done  

[ $i -eq 0 ]という部分が式です。
この式では変数iの値が0と等しいかどうか比較しています。
変数iの値は0なので、この比較は常にtrueになります。
よって↑のwhile文の判定は常にtrueになるので、延々とループ処理が実行されることになります。
「0です。」という出力が無限に出力されるため、↑のコードを実行した場合はCtrl+Cなどでスクリプトを終了してください。

判定部分の式の正体

このようにwhile文には判定部分に式を使うことができます。
この判定部分の式は正確にはtestコマンド、または[コマンドです。
角カッコ([])の[はコマンドで、testコマンドとほぼ同じ挙動をするコマンドです。

[コマンドは最後の引数の]を無視するため、角カッコで式を囲んだような表記が可能になります。
$i -eq 0の部分は[コマンドの引数です。
[コマンドはこれらの引数を評価して返り値、つまり終了ステータスを返します。

ためしに↓のコードを実行してみてください。

i=0  
[ $i -eq 0 ]  
echo $?  

[ $i -eq 1 ]  
echo $?  

コマンドの終了ステータスは$?変数で参照することが可能です。
↑のように[コマンドを実行した後にecho$?を参照すると、↓の結果になります。

0  
1  

これはtestコマンドの挙動と同じです。
試しにtestコマンドを↓のように実行してみます。

i=0  
test $i -eq 0   
echo $?  

test $i -eq 1   
echo $?  

結果は↓になります(先ほどと同じです)。

0  
1  

testコマンドと[コマンドの大きな違いは、先ほども述べましたが最後の]を無視するかしないかの違いです。
testコマンドは最後の]を無視しないので、たとえば↓のようなコマンドはエラーになります。

i=0  
test $i -eq 0 ]  

出力結果↓。

sample.sh: line 2: test: too many arguments  

testコマンドはタイプ数が多いため、while文などでは[コマンドのほうが使われることが多いです。
しかしもちろん↓のようにtestコマンドをwhile文に使うこともできます。

i=0  
while test $i -eq 0; do  
    echo "iは0です。"  
done  

角カッコを使った表記のほうが人の視覚的に見やすいため、一般的には[のほうが多用されます。
しかしtestコマンドでも代用が効くというところは頭の片隅にでも置いておいてください。

ファイルの内容を1行ずつ読み込む

while文でファイルの内容を1行ずつ読み込みたい場合は↓のようなフォーマットになります。

while read 変数名; do  
    処理  
done < ファイル名  

キーワードの「while」のあとにreadコマンドを書きます。
readコマンドはBashの組み込みコマンドで、標準入力からの入力を1行読み込んで、引数の変数に格納します。
セミコロン、doと処理内容、doneキーワードを書きます。
doneキーワードのあとに小なり(<)を書き、読み込ませたいファイル名を書きます。

↑の「ファイル名」の部分のファイルがオープンされ、その内容が一行ずつreadコマンドによって読み取られます。
読み込んだ内容はread 変数名の「変数名」に格納され、それをdodoneの間の処理部分で使うことができます。

たとえばanimals.txtというファイルを読み込みたい場合は↓のようにします。

while read name; do  
    echo $name  
done < animals.txt  

実行結果。

cat  
dog  
bird  

animals.txtというファイルは↓のように動物の名前が英語で一行ずつ羅列されているファイルです。

cat  
dog  
bird  

このファイル内の行がreadコマンドで一行ずつ読み取られていくわけですね。
read nameとしているので、読み込んだ行は$nameで参照することが可能です。
↑のコードでは$nameechoで参照して出力しています。

無限ループ

while文の無限ループは↓のように書きます。

while true; do  
    処理  
done  

Bashではtrueコマンドやfalseコマンドを使うことができます。
これ、コマンドなんです。
trueコマンドはなにもしませんが、終了ステータスを0で返します。
falseコマンドは終了ステータスを1で返します。

true  
echo $?  

false  
echo $?  

実行結果↓。

0  
1  

while文の判定部分にこれらtrueコマンドやfalseコマンドを使うことができます。
trueコマンドを使った場合は、その返り値(終了ステータス)が判定に使われます。
trueコマンドは常に終了ステータス0を返すため、while文でtrueコマンドを使うと無限ループになります。

↓は1秒ごとに時刻を表示するサンプルです。

while true; do  
    date  
    sleep 1  
done  

使用例

Bashのwhile文の使用例です。

カウント変数をカウントする

カウント変数を0から4までカウントするサンプルです。

i=0  

while [ $i -lt 4 ]; do  
    ((i++))  
done  

echo $i  
# 4  

[ $i -lt 4 ]という[コマンドは、変数$iの値が4より低いかどうか評価しています。
変数$iは最初に0で初期化され、while文の繰り返し処理の中で((i++))でインクリメントされます。
変数$iの値が4以上になったら[ $i -lt 4 ]の結果はfalse, つまり1になるので、while文が終了します。

while文が終了した時点で変数$iの値は4になっているので、これをechoで出力しています。

CSVファイルを読み込み再構成して表示する

CSVファイルから1行ずつ読み込んでデータを再構成して表示します。

while read line; do  
    echo $line | awk -F, '{print "名前", $1, "年齢", $2, "体重", $3}'  
done < animals.csv  
# 名前 cat 年齢 10 体重 30  
# 名前 dog 年齢 20 体重 40  
# 名前 bird 年齢 70 体重 4  

animals.csvファイルは↓のようなフォーマットのファイルです。

:text cat,10,30 dog,20,40 bird,70,4

先頭の列から名前、年齢、体重を表しています。
このファイルをwhile文のreadコマンドで1行ずつ読み込みます。

echo $line | awk -F, '{print "名前", $1, "年齢", $2, "体重", $3}'

というコマンドは、行の内容を標準出力に出力し、awkに渡しています。
awkではCSVのフォーマットの文字列を分解し、その列を$1, $2, $3で参照しています。
awkprintでそれらの列をラベルを付けて出力しています。

問題

Q1: while文の構成部品をすべてあげよ

  1. 判定と処理
  2. 初期化と判定と更新と処理
  3. 処理

Q2: while文で1行ずつファイルを読み込む場合ファイル名はどこに指定するか答えよ

  1. whileの後
  2. doの後
  3. doneの後

Q3: while文で無限ループするときの判定部分として正しいものを答えよ

  1. true
  2. false
  3. [ 0 -eq 1 ]

正解

Q1: 1
Q2: 3
Q3: 1