rokkonet

PC・Androidソフトウェア・アプリの開発・使い方に関するメモ

テキストファイルから1行ずつ読み込む bashシェルスクリプト

2021 Mar. 07.
2021 Mar. 02.

使えるコード例
while read Line; do
  echo $Line
done < FILE/PATH

意図しない変数の値となるかもしれないコード例
## パイプの中のwhileループはサブシェルでの実行となり、
## その中での変数値の変更はwhileループの外に反映されない
cat FILE/PATH | while read Line; do
  echo $Line
done


変数値変更の注意
Str=""
CountLine=0
while read Line; do
  CountLine=$(( CountLine + 1 ))
  echo "Line: $Line"
  Str="$Line"
  if [ $CountLine -eq 1 ]; then
    break
  fi
done < FILE/PATH
echo "Str: $Str"

## Strは"$Line"と同じ内容になっている。
Str=""
CountLine=0
cat FILE/PATH | while read Line; do
  CountLine=$(( CountLine  + 1 ))
  echo "Line: $Line"
  Str="$Line"
  if [ $CountLine -eq 1  ]; then
    break
  fi
done
echo "Str: $Str"

## Strは""のままとなっている。


カレントシェル/サブシェル/変数

COMMAND1 | COMMAND2 とパイプを使うとCOMMAND2はサブシェルで実行される。
サブシェルはカレントシェルのコピーで、カレントシェルのシェル変数・環境変数はサブシェルでは同じ変数名・同じ値がセットされる。
サブシェルからカレントシェルの変数にアクセスすることはできない。
サブシェルが終了するとサブシェルの変数は破棄される。
カレントシェルの変数の値をサブシェル内で変更したつもりでもカレントシェルに戻るとその値はサブシェル起動前の値のままである。

whileループや if 文などの制御構造をリダイレクトする場合もサブシェルが使われることがあるが、それはシェルのバージョンに依存する。
なお、リダイレクト指定しない制御構造はカレントシェルで実行される。

"cat FILE | while read Line; do echo $Line; done" ではサブシェルが使われる。
"while read Line; do echo $Line; done < FILE" でのサブシェル使用は環境依存。

Ubuntu 20.04のbash バージョン 5.0.17、dashでは while do done < FILE のループ内での変数変更がループ外で参照できた。

リダイレクトを避け、環境依存しないwhileループ 1

## http://www.nurs.or.jp/~asada/FAQ/UNIX/section3.8.html より

foo=bar

# ファイル記述子9をファイル記述子0 (標準入力) の複製にする。
# その後、標準入力を /etc/passwd に接続する; もとの標準入力は
# ファイル記述子9に「記憶」されている; dup(2)、sh(1) を参照。
exec 9<&0 < /etc/passwd

while read line
do
    # $line に対して何かする。
    foo=bletch
done
# 標準入力をファイル記述子9の複製にする。つまり、標準入力を
# もとの標準入力に接続し直す; その後、ファイル記述子9を
# クローズする。
exec 0<&9 9<&-

echo "foo is now: $foo"

リダイレクトを避け、環境依存しないwhileループ 2

## http://pcmemorin.blog.fc2.com/blog-entry-455.html より

## 標準入力とファイルからの入力とを別のファイルディスクリプタを使って扱うようにします。

exec 3<&0 < file
while read LINE
do
・・・・・・・・・
done
exec 0<&3 3<&-

## ①最初のexecで一時的にファイルディスクリプタを3番を標準入力からにし、標準入力をファイルからの入力にしています。
##  3番でキーボードからの入力を読み取り、標準入力はファイルからの入力にしました。
## ②whileの処理が終わった後、標準入力をファイルディスクリプタ3番、つまりキーボード入力に戻し、3番を閉じます。


## またこのような方法もあります

exec 3< file

while read LINE 0<&3
do
・・・・・・
done
exec 3<&-


execコマンドについて
リダイレクト・ファイル記述子複製を永続的に設定できる。

ファイル記述子3を利用してFilePathをreadコマンドで読み込む例
execコマンドでファイル記述子3の読み込み元をFilePathにし、readコマンドの読み込み元である標準入力(ファイル記述子0)の読み込み元をファイル記述子3の読み込み元(FilePath)と同じにしてreadコマンドでの読み込みを行っている。
最後にファイル記述子3を閉じてスクリプトを終えている。

exec 3< FilePath
while read row 0<&3; do
  echo $row
done
exec 3<&-


参考ページ
UNIX FAQ LIST #3.8
https://flex.phys.tohoku.ac.jp/texi/sh/node58.html
exec コマンド – シェルを実行したコマンドで置き換える | Linuxコマンド.NET
シェルスクリプト exec コマンド - Qiita

リダイレクト・ファイル記述子について

  • 0< FILE :標準入力の読み込み元をFILEに切り替える
  • 1> FILE :標準出力の書き出し先をFILEに切り替える
  • 1> /dev/tty :標準出力先を画面に切り替える
  • 3<&0 :ファイル記述子3の読み込み元をファイル記述子0の読み込み元と同じにする。これは「ファイル記述子0をファイル記述子3に複製する」と表現される
  • 3<&- :読み込みファイル記述子3を閉じる
  • 4>&1 :ファイル記述子4の書き出し先をファイル記述子1の書き出し先と同じにする。これは「ファイル記述子1をファイル記述子4に複製する」と表現される
  • 4>&- :書き出しファイル記述子4を閉じる


参考サイト
シェルとファイルデスクリプタのお話 - Qiita
# シェルスクリプトにおけるファイル記述子 - Qiita

プロセス置換によるサブシェル回避

Linuxでのプロセス置換 - Qiita より
A | while read VAR; do シェル変数設定; done のような複合コマンドとパイプラインが混在するケースの場合、途中で設定したシェル変数を大本のシェルに反映させることができません。これは、パイプラインを構成する各コマンドが子シェルの中で実行されるためです。
しかしこれを、while read VAR; do シェル変数設定; done < <( A ) のプロセス置換に書き直してパイプラインを解消することで、大本のシェルに反映させられる形にできます

参考ページ
プロセス置換とコマンド置換 bashシェルスクリプト - rokkonet