■レシピ6.10    whileループ

 

【例1】

while (( 1 >= 0 ))

do

 echo これは永久ループです

done 

 

【例2】

while  0
do
echo これは永久ループです
done

上記を実行すると下記のようなエラー出力になる。

$ bash ./test.sh
./test.sh: line 1: 0: command not found

→while文の引数のリストはコマンドで、その終了ステータスが0の場合はtrueと見なされてループが実行される。この部分はif文と同じ。

 

【例3】

$ ls a

a         ←aというファイルが存在する。

$ cat test1.sh

while ls a
do
echo これは永久ループです
done

上記を実行すると期待通り永久ループとなって、

a

これは永久ループです

が延々と出力される。

 

【例4】cat test2.sh

while [ -e a ]
do
echo これは永久ループです
done

上記を実行すると期待通り永久ループとなる。つまり、while文の引数リストをtestコマンドにしてもよい。

つまり、while文の部分は下記の場合すべてOK

while [ 0 == 0 ]

while [ 0 = 0 ]

while [ 0 -eq 0 ]

while test 0 == 0

 

■複合コマンド「 (( )) 」

$(( ))と似ていて算術式を評価する。$(())は算術式を評価して変数値を返すが、(())は算術式を評価して終了ステータスを返す。2重括弧の中に変数がある場合は変数の前に「$」をつけなくてもよい。

 

【例5】

$ a=1;if ((a<=0));then echo true;else echo false;fi
false
$ a=1;if ((a>=0));then echo true;else echo false;fi
true

 

【例6】

if (( 0 == 0 ));then

echo 真

fi

上記を実行すると「真」が出力される。

斜体部分を下記のようにしても「真」が出力される。

if ((0==0))       ←(( ))の中のホワイトスペース無し

f ((0<=1))

f ((0<1))

また、斜体部分を下記のようにしたらエラーになる。

f (( 0 = 0 ))         ←エラー

f (( 0 -eq 0 ))      ←エラー

複合コマンド (( )) の中はあくまで算術式で、testコマンドと違って、「==」は許されるが「=」は許されない。また、testコマンドの場合は、数値の比較は「-eq」で文字列の比較は「=」または「==」なところも違う。

 

【例7】

$ ((0==0))

$ echo $?
0
$ ((0==1))
$ echo $?
1

$ ((0))
$ echo $?
1     ←終了コードが1になる!
$ ((1))
$ echo $?
0           ←終了コードが0になる! ((0以外の数字))
$ ((hoge))
$ echo $?
1     ←終了コードが1になる!((文字列))は文字列が変数と解釈されて値がnullだから?

$ (())
$ echo $?
1          ←((null))は終了コードが1になる! 

$ ((777))
$ echo $?
0           ←終了コードが0になる! ((0以外の数字))

$ ((-1))
$ echo $?
0           ←終了コードが0になる! ((負整数))
$ ((0.1))
-bash: ((: 0.1: syntax error: invalid arithmetic operator (error token is ".1")      ←((実数))はエラー

 

上記から下記も永久ループになる

while ((1))

do

echo これは永久ループです

done

※(())の引数の変数や式の値に真偽値が対応するのはperlの真似?

ちなみにperlの場合は文字列と数のいずれでも値に真偽値が対応し、「0」やNullなら偽、左記以外なら真。ただしperlの場合、真値は1で偽値は0

 

■レシピ6.10~11  while文とreadコマンド

1.標準入力から読み込んでループ

【例8】

$ cat read_while.sh

#!/bin/bash
while read X
do
echo "これは$Xです"
done

$ ./read_while.sh
a
これはaです
b
これはbです
c
これはcです
^C
 

2.ファイルからスクリプトにredirect

【例9】

$ cat seq.txt
1
2
3
4
5

$ ./read_while.sh < seq.txt
これは1です
これは2です
これは3です
これは4です
これは5です

 

3.スクリプトの中でファイル出力をwhile文にパイプで渡す

【例10】

$ cat read_while2.sh
#!/bin/bash
cat seq.txt | while read X
do
echo "これは$Xです"
done

$ ./read_while2.sh
これは1です
これは2です
これは3です
これは4です
これは5です

 

4.スクリプトの中でファイルをdoループにリダイレクトで渡す

【例11】

$ cat read_while3.sh
#!/bin/bash
while read X
do
echo "これは$Xです"
done < seq.txt

 

$ ./read_while3.sh
これは1です
これは2です
これは3です
これは4です
これは5です

 

5.サブシェル、シェル、親シェルなどのpid

http://qiita.com/laikuaut/items/1daa06900ad045d119b4

■$$    

シェルのプロセスIDに展開されます。()を使ったサブシェルの内部では、$ はサブシェルではなく、現在のシェルのプロセス ID に展開されます。
■$!    

最後に実行されたバックグラウンド (非同期) コマンドの プロセス ID に展開されます。

■$BASHPID    

現在の bash のプロセス ID に展開されます。 bash を再初期化しないサブシェルのような、いくつかの環境においては、 $$ と値が異なります。
■$PPID    

そのシェルの親のプロセス ID。この変数は読み込み専用です。

 

【例12】

$ echo "'$$='$$  '$PPID='$PPID '$!='$! '$BASHPID='$BASHPID ここからサブシェル $(echo '$$='$$  '$PPID='$PPID '$!='$! '$BASHPID='$BASHPID)"

出力結果は、
$$=25118 $PPID=25111 $!= $BASHPID=25118 ここからサブシェル $$=25118 $PPID=25111 $!= $BASHPID=26869

※上記のechoコマンド実行では、「$$」にはechoコマンドを実行したbashのPID、「$PPID」にはbashの親プロセスである「sshd: root@pts/1」のPID、「$!」には直前で終了したバックグラウンドジョブがないので値無し、$BASHPID」には「$$」と同じくechoコマンドを実行したPIDが入る

※上記のサブシェル「$()」内のechoコマンド実行では、「$$」にはサブシェルではなくカレントシェルのPID、「$PPID」にはカレントシェルの親プロセスである「sshd: root@pts/1」のPID、「$!」には直前で終了したバックグラウンドジョブがないので値無し、$BASHPID」にはサブシェルのPIDが入る。

※つまりスクリプト内のコマンドがサブシェルで実行されているかどうかは$BASHPIDの値を見ればよい!

 

【例13】

■「;」による連結と$BASHPID

$ echo '$$='$$ '$BASHPID='$BASHPID;echo '$$='$$ '$BASHPID='$BASHPID
$$=25118 $BASHPID=25118
$$=25118 $BASHPID=25118

→「;」によって連結された前後のコマンド実行シェルは当然同じ

■「{コマンド;}」によるコマンド連結と$BASHPID

$ echo '$$='$$ '$BASHPID='$BASHPID;{ echo '$$='$$ '$BASHPID='$BASHPID;echo '$$='$$ '$BASHPID='$BASHPID; }
$$=25118 $BASHPID=25118

$$=25118 $BASHPID=25118
$$=25118 $BASHPID=25118

→「{コマンド;}」の中はサブシェルではない

■「$(コマンド)」はサブシェルで実行されている

$ echo '$$='$$ '$BASHPID='$BASHPID;echo $(echo '$$='$$ '$BASHPID='$BASHPID)
$$=25118 $BASHPID=25118
$$=25118 $BASHPID=27384

→【例12】でみたように、「$()」内のコマンドはサブシェルで実行されている

 

【例13】

■ループ内外の変数1

$ cat read_while2.sh
#!/bin/bash
export X
echo 'whileループ外の$BASHPIDは' $BASHPID
cat seq.txt | while read X
do
echo $X 'whileループ内の$BASHPIDは' $BASHPID
done

echo "Xの値は$Xです"
$ ./read_while2.sh
whileループ外の$BASHPIDは 27649
1 whileループ内の$BASHPIDは 27651   ←Whileループを実行してるbashはスクリプトを実行してるbashのサブシェル
2 whileループ内の$BASHPIDは 27651
3 whileループ内の$BASHPIDは 27651
4 whileループ内の$BASHPIDは 27651
5 whileループ内の$BASHPIDは 27651
Xの値はです   ←親シェルでexportしたXにサブシェルで値を入力しても親シェルには反映されない (親→子へは値渡しなので)

■ループ内外の変数2

$ cat read_while3.sh
#!/bin/bash
export X
echo 'whileループ外の$BASHPIDは' $BASHPID
while read X
do
echo $X 'whileループ内の$BASHPIDは' $BASHPID
done < seq.txt
echo "Xの値は$Xです"

$ ./read_while3.sh
whileループ外の$BASHPIDは 27653
1 whileループ内の$BASHPIDは 27653      Whileループを実行してるbashとスクリプトを実行してるbashは同じシェル
2 whileループ内の$BASHPIDは 27653
3 whileループ内の$BASHPIDは 27653
4 whileループ内の$BASHPIDは 27653
5 whileループ内の$BASHPIDは 27653
Xの値はです  ←whileループ内はループ外と同じシェルなのに、ループ終了後なぜかループ内の値が消えている!!!

 

【例14】

■ループ内外の変数3

$ cat for.sh
#!/bin/bash
#export X      ←そもそもforループではループ内外で同じシェルなのでループの外でXをexportする必要はない
echo 'forループ外の$BASHPIDは' $BASHPID
for X in `cat seq.txt`
do
echo $X 'forループ内の$BASHPIDは' $BASHPID
done
echo "Xの値は$Xです"

$ ./for.sh
forループ外の$BASHPIDは 28226
1 forループ内の$BASHPIDは 28226  ←forループ内はサブシェルではない
2 forループ内の$BASHPIDは 28226
3 forループ内の$BASHPIDは 28226
4 forループ内の$BASHPIDは 28226
5 forループ内の$BASHPIDは 28226
Xの値は5です                ←while文とreadのときとちがってループ内の値が保持されている!

■whileループ内の変数の流れを詳細に調査

$ cat read_while4.sh

#!/bin/bash
#ループ内外の変数を配列を使って調べるんご
#変数確認用配列初期化
for k in 1 2 3 4 5 6 7
do
array[$k]=7           ←配列の初期値はすべて「7」にしる
done
#whileループ外のbashのPID
echo 'whileループ外の$BASHPIDは' $BASHPID
#while-readループ
i=1           #変数確認用
while read X
do
echo $X 'whileループ内の$BASHPIDは' $BASHPID
array[$i]=$X  #変数を配列に格納
let i++
done < seq.txt
#ループ内でXに最後に格納された値「5」なはず?
echo "Xの値は$Xです"
#ループ内でXの値を格納した配列を出力
for j in 1 2 3 4 5 6 7
do
echo ${array[$j]}
done

$ ./read_while4.sh
whileループ外の$BASHPIDは 28785
1 whileループ内の$BASHPIDは 28785
2 whileループ内の$BASHPIDは 28785
3 whileループ内の$BASHPIDは 28785
4 whileループ内の$BASHPIDは 28785
5 whileループ内の$BASHPIDは 28785
Xの値はです       ←ループ内でXに最後にセットされた「5」がループ外では保持されてない!
1
2
3
4
5         
 ←ループ内で最後にXに入力された値は「5」。EOFとかじゃない!
7            ←同上
7

 

※「bashクックブック P.139」の検証

seq.txtの出力をパイプでwhile-readループに渡した場合は、whileループ内はサブシェルなのでループ内で代入した値はループ外では保持されないと書かれていたが、こちらは上述のように親bashから子bashへは値渡しなので、たとえ親bashで変数をexportしてても、子bashで入力した値が親bashに渡らないというのは検証できた。

しかし、seq.txtファイルをwhile-readループにリダイレクトで渡した場合には、whileループ内はループ外と同一シェルなのでループ内で代入した値はループ外でも保持されることが示唆されていたが、実際には、while-readループ内で代入した変数値はループ外では保持されなかった。

ちなみに、forループに関しては、ループ内で代入した値はループ外でも保持された。

 

【例15】 readコマンドで一度に複数のトークンを読み込ませる

 

$ cat read_while.sh
#!/bin/bash
while read X Y Z         ←readコマンドの引数が複数
do
echo "1個目 " $X " 2個目 " $Y " 3個目 " $Z
done < text

$ cat text
1 a A
2       b B BB            ←第一トークンと第二トークンの間がtab
3 c     C CC      ←第二トークンと第三トークンの間がtab
4 d D   DD       ←第三トークンの中にtabが含まれる 
5,f F           ←第一トークンと第二トークンの区切り文字がコンマ(のつもり)  
6,g,G           ←全トークンがコンマ(のつもり)  
7;h;h           ←全トークンがセミコロン(のつもり) 

"あい う" え お か

"a      b"              
"c d"

'e f'

"a" "b" "c"
$ ./read_while.sh
1個目  1  2個目  a  3個目  A
1個目  2  2個目  b  3個目  B BB        ←スペース同様tabも区切り文字と見なされた
1個目  3  2個目  c  3個目  C CC        ←同上
1個目  4  2個目  d  3個目  D DD        ←第三トークンの中のtabが勝手にスペースに変換された
1個目  5,f  2個目  F  3個目            ←コンマは区切り文字と見なされなかった
1個目  6,g,G  2個目   3個目            ←同上
1個目  7;h;h  2個目   3個目            ←セミコロンも区切り文字と見なされなかった

1個目  "あい  2個目  う" え 3個目  お か

1個目  "a  2個目  b"  3個目                         ←「" "」で囲われたホワイトスペースも区切り文字と見なされた
1個目  "c  2個目  d"  3個目

 1個目  'e  2個目  f'  3個目                           ←「' '」で囲われたホワイトスペースも区切り文字と見なされた

1個目  "a"  2個目  "b"  3個目  "c"

 

 

■レシピ6.12~13 forループ

【例1】

$ for ((i=0;i<5;i++));do echo $i;done
0
1
2
3
4 
 

 

■再び複合コマンド「(( ))」の謎

$ type \(\(
-bash: type: ((: not found
$ type \(
-bash: type: (: not found

$ type {
{ is a shell keyword

$ type [
[ is a shell builtin
$ type [[
[[ is a shell keyword

$ ((1));echo $?

0
$ ((i=1));echo $?
0
$ echo $i
1
$ unset i
$ ((i=1;i<5));echo $?
-bash: ((: i=1;i<5: syntax error: invalid arithmetic operator (error token is ";i<5")
1
$ echo $i
               ←iに値は格納されてない

$ ((i=1;i<5;));echo $?
-bash: ((: i=1;i<5;: syntax error: invalid arithmetic operator (error token is ";i<5;")
1
$ echo $i
               ←iに値は格納されてない

$ ((i=1;i<5;i++));echo $?
-bash: ((: i=1;i<5;i++: syntax error: invalid arithmetic operator (error token is ";i<5;i++")
1

$ echo $i

               ←iに値は格納されてない

※if文やwhile文の引数リストに表れる二重括弧「(( ))」は算術式を評価して論理値を返す複合コマンドということで説明がついたが、if文の引数リストに表れる二重括弧「(( ))」はこれらと毛色がちがうぞ! for文の引数リストだけ、fortranやperlのようなプログラム開発言語のように書式が決められた構文と見なした方がよいのか?

そもそも「for」、「if」、「while」とはbashにとって何なのさ?

$ type for
for is a shell keyword
$ type if
if is a shell keyword
$ type while
while is a shell keyword

さらに調子に乗って調べてみる

$ type do
do is a shell keyword
$ type done
done is a shell keyword
$ type fi
fi is a shell keyword
$ type else
else is a shell keyword
$ type elif
elif is a shell keyword

これらはbashの組み込みコマンドではなくbash keywordとのこと

 

そのほかのforループ

 

【例2】

for i in 0 1 2 3 4     「in」のkeywordの右側にリストを並べる

do

echo $i

done 

ちなみに「in」は、

$ type in
in is a shell keyword

 

【例3】

for i in `seq 0 4`    

do

echo $i

done

 

【例4】 2つの変数(iとj)を初期化し、インクリメントするforループ

for (( i=0,j=0 ; i+j < 10 ; i++,j++ ))     ←コンマを使って複数の式を一つにまとめることができる

do

echo $i $j

done

 

【例5】 浮動小数点数値によるループ (for版)

for i in `seq 0.0 .1 1.0`    

do

echo $i

done

実行結果は、

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1.0

 

【例6】 浮動小数点数値によるループ (while版)

seq 0.0 .1 1.0 | while read X

do

echo $X

done

 

※【例5】の場合、seqコマンドを最後まで実行してから、その出力全体をfor文のコマンドラインに配置するのでメモリを消費する。【例6】の場合はseqコマンドとwhileは並行して実行されるのでメモリを消費しない。