先日久しぶりにbashスクリプトを書いたときにレビューで指摘いただいて初めて知ったことなので書き留めておく。
bash スクリプトで1行ずつ読み込んでwhile read lineを使うときの構文。
今までこのように、while read lineの前に変数やテキストファイルを展開して読ませていました。
$ cat front.sh #/bin/bash LIST="aaaa bbbb cccc dddd" echo "${LIST}" | while read line 👈ここに注目! do echo "====LOOP=====" echo "$line" DEFINE_IN_LOOP="TEST" done echo "$DEFINE_IN_LOOP"
さてこれを実行すると以下の通りになります。
====LOOP===== aaaa ====LOOP===== bbbb ====LOOP===== cccc ====LOOP===== dddd
echo "$DEFINE_IN_LOOP"
の結果が出てきません。これはパイプでLISTの内容を渡しているのでサブシェルでループが走り、ループを抜けると中で定義された変数はなかったことにされるからですね。
なのでパイプを使わずにこう書くのがよさそう。
$ cat back.sh #/bin/bash LIST="aaaa bbbb cccc dddd" while read line 👈パイプを使わないように変えた do echo "====LOOP=====" echo "$line" DEFINE_IN_LOOP="TEST" done < <(echo "${LIST}") echo "$DEFINE_IN_LOOP"
これであればループの中で定義した変数をループから抜けても取り出せます。
====LOOP===== aaaa ====LOOP===== bbbb ====LOOP===== cccc ====LOOP===== dddd TEST
この記法はbashじゃないとできないことに注意(shではできない)
sh back.sh back.sh: 行 13: 予期しないトークン `<' 周辺に構文エラーがあります back.sh: 行 13: `done < <(echo "${LIST}")'
★追記 コメントいただきました🙏
変数がなかったことにされるは、正確にはパイプで実行されたwhileがサブシェルになるのでローカル変数への変更が保持されないって感じ。
— ぶてい (@buty4649) 2021年12月27日
whileの中でsleepして、psで見るとwhileのbashプロセスが別で起動されているのがわかると思う。
というわけで確かめてみます。 ループ処理の中にsleepを入れてps で確認。
こちらがパイプを使っているほう
vagrant 6345 0.0 0.2 157044 2616 ? S 12月27 0:00 \_ sshd: vagrant@pts/0 vagrant 6346 0.0 0.2 115688 2160 pts/0 Ss 12月27 0:00 \_ -bash vagrant 24285 0.0 0.1 113280 1360 pts/0 S 01:20 0:00 \_ bash front.sh vagrant 24287 0.0 0.0 113284 644 pts/0 S 01:20 0:00 | \_ bash front.sh vagrant 24288 0.0 0.0 108052 360 pts/0 S 01:20 0:00 | \_ sleep 5 vagrant 24293 0.0 0.1 155600 2012 pts/0 R+ 01:20 0:00 \_ ps auxfww
こちらがパイプを使わないほう
vagrant 6345 0.0 0.2 157044 2616 ? S 12月27 0:00 \_ sshd: vagrant@pts/0 vagrant 6346 0.0 0.2 115688 2172 pts/0 Ss 12月27 0:00 \_ -bash vagrant 24362 0.0 0.1 113284 1404 pts/0 S 01:22 0:00 \_ bash back.sh vagrant 24371 0.0 0.0 108052 360 pts/0 S 01:22 0:00 | \_ sleep 5 vagrant 24372 0.0 0.1 155600 2012 pts/0 R+ 01:22 0:00 \_ ps auxfww
確かに、パイプを使わないほうはサブシェルが起動されていないことがわかります。