private tips

プログラミングとその他諸々の備忘録。

【bash】の while の中で read したいとき

 はじめに

bash で、while に入力用のファイルを一行ずつ読み込ませて処理の結果を表示するかしないかを対話形式で決めて表示する、というようなスクリプトを書いていて、躓いたので備忘録。

 

例として、次のようなテキストファイル ( IPアドレス ホスト名 ) があるとする。

f:id:zvub:20170211111728p:plain

今回やりたかったのは、このファイルを1行ずつ読み込んで、表示の際にIPアドレスの網掛け行うかどうかをキーボード入力により決定する、というもの。

 

当初の bash スクリプトの中身は以下。

#!/bin/bash

### Sub-shell verification code ###

HOME=/opt/scripts
TARGETS=${HOME}/sample.txt

while read LINE
do
ADDR=`echo $LINE | awk -F' ' '{print $1}'`
NAME=`echo $LINE | awk -F' ' '{print $2}'`
echo -e -n "Do you want to display the IP address ?(yes/no) : "
read -r ANSWER
if [ "$ANSWER" == 'yes' ] || [ "$ANSWER" == 'y' ] ; then
echo "IP address : ${ADDR} , Hostname : ${NAME}"
else
echo "IP address : xx.xx.xx.xx , Hostname : ${NAME}"
fi
done < $TARGETS

while にファイルをリダイレクトするかたちで読み込んで、

while の中で read する事で、IP アドレスを表示する際に網掛けを行ったり行わなかったりを決定して表示を行いたかった。

が、何も考えずに実行すると下のようになる。

失敗例

f:id:zvub:20170218110349p:plain

冒頭に示したテキストファイルを1行ずつ読み込んで処理を行っているわけだけど、一周目の while の中の read でテキストファイルの2行目が標準入力として読まれてしまっている。

結構ハマりました。

原因

 何が駄目だったのかというと、while ループにリダイレクトしているファイルが、標準入力扱いになるからだった。

UNIX系OSにはファイル・ディスクリプタと呼ばれるものがあり、それによって、プロセスとそれが利用するファイル等を結びつける役割を担っている。それぞれ

標準入力:0

標準出力:1

標準エラー出力:2

という風に、0 からの 3つが既に予約されている。

 

つまり、上記のスクリプトの場合、while do ~ done の後ろでリダイレクトしているテキストファイルが標準入力【0】に入るのに対し、while ループ内の read でも、受け取る入力が標準入力【0】であるために失敗した。ということになる。

 

対策

対策と呼べるほどでもないかと思うけれど、どうやって回避したのか、という結果のスクリプトが以下になる。

#!/bin/bash

### Sub-shell verification code ###

HOME=/opt/scripts
TARGETS=${HOME}/sample.txt

while read LINE <&3
do
ADDR=`echo $LINE | awk -F' ' '{print $1}'`
NAME=`echo $LINE | awk -F' ' '{print $2}'`
echo -e -n "Do you want to display the IP address ?(yes/no) : "
read -r ANSWER
if [ "$ANSWER" == 'yes' ] || [ "$ANSWER" == 'y' ] ; then
echo "IP address : ${ADDR} , Hostname : ${NAME}"
else
echo "IP address : xx.xx.xx.xx , Hostname : ${NAME}"
fi
done 3< $TARGETS

while read LINE の後ろ "<&3" と、末尾 done 句の後ろ "3<" に注目して欲しい。

ここで何を行っているかというと、

上記で説明した"予約済みのファイル・ディスクリプタ"以外のファイル・ディスクリプタ、3番を利用している、という記述になる。

つまり3番目のファイル・ディスクリプタにファイルをリダイレクトし、while read 部で3番目のファイル・ディスクリプタから入力を読み込む事により、「標準入力を使用しない」while 文になっているわけである。

そうすると安全に while の中で標準入力が扱えるようになり、以下のように想定した動作を行うことができるようになる。

f:id:zvub:20170218113458p:plain

 

対策として不十分な気がしないこともないけど、回避策としては十分かな、ということで備忘録でした。