シェルスクリプトを使っていて、たまにコマンドラインで
入力文字が表示されなくなったりすることがあって、
その原因の1つがread
を-s
(silent)で立ち上げた状態で
ctrl-Cで止めてしまってた事だったことがわかったので
その処理についてのまとめ。
問題が起きる条件
通常、シェルスクリプトで
1 2 3 4 5 |
|
みたいに読み込み文字を表示させないで入力を取り込もうとして、
このread
の待ちの状態でctrl-Cを押しても
特に問題は起こりません(ただエラー終了するだけです)。
ただし、このread
部分を関数にして、さらにその関数をパイプ後に呼ぶと問題が
起こります。
1 2 3 4 5 6 7 8 |
|
入力をパイプと分ける為に</dev/tty
を加えてあります。
これを実行すると、上の関数で無いものと同じ様に、一文字受け取ってそれを
表示するだけなんですが、
read
の部分で止まっている時、ctrl-Cで止めてしまうと、
コマンドラインで打ち込んでも文字が表示されません。
実際にキーは入力されてるので適当にls <Enter>
とかすれば表示されますが、
コマンドは表示されません。
ここで、stty echo
を実行するときちんとコマンドも表示されるようなります。
サブシェルを呼ぶと問題なのか?と思ったんですが、
ret=$(f_read)
の様にした場合は途中で終了させても問題ないのでパイプにした時だけです。 パイプを使うと標準入力が切り替わったりするので、その辺で問題になるのかと。 (いまいち原理が理解できない。。。)
さらに組み合わせとして、
ret=$(echo test|f_read)
とした場合や
echo test|ret=$(f_read)
とした場合も問題が置きます。
さらに、trap
でHUP信号等が来た時の動作を決めた場合でも様子が変わります。
1 2 3 4 5 6 7 8 9 |
|
とすると、この場合は問題が起きません 1。
ただし、パイプだけ:echo test |f_read
や、中にパイプ:(echo test|f_read)
の場合は上の様にしても駄目です。
解決法
解決法というほどではないですが、終了後にstty echo
をすれば戻るので、
最後にかならずこれを呼んであげれば良い、というだけです。
上のスクリプトなら一番簡単な方法は
1 2 3 4 5 6 7 8 9 |
|
とすれば良いだけ2。
ただし、このままだとこのスクリプト自体をパイプ後に入れると、 正しく終了した場合でもエラーが必ず出てしまいます。
stty echo
を標準入力がパイプだったりターミナルと結びついてない状態で行うと、
$ echo test|stty echo
stty: stdin isn't a terminal
こんな感じのエラーに。 さらに、ctrl-Cで止めた場合には エラーが出る上に上の出力が止まる問題が発生します。
これを回避するために関数内でtrapをかけます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
これだと、上のコメントしてある場合も含めてすべての場合で 途中でctrl-Cで止めても、 その後でコマンドライン入力がきちんと表示されます。
ただし、この場合には、$()
を使った場合、
ctrl-Cで止めると
stty: tcsetattr: Input/output error
というエラーが出ます。 この場合は、元々出力に問題がないものなので、終了後には コマンドもきちんと表示されますが、この処理がどうしても回避出来ませんでした。
差し当たり、問題が無いようなら、
trap "stty echo 2>/dev/null;return" 1 2 3 15
みたいにしてエラーメッセージを表示させない、ということまでは出来ます。
色々試してみた結果、今のところこんな感じ。
下のGistに色々試したスクリプトとメモが置いてあります。