落書き帖  古事記  いろは歌  日 記

Redirection , File Descriptor and Pipe in the Shell

2016年04月20日
この間分から無かった、readの挙動が何となく分かってきた。
分からなかった時書いた記事↓

readは標準入力から読み込むの?

readって本当に標準入力から読み込むのか?
って疑問に思ってったんだけど、その原因は↓

echo tama | read a | echo $a

これがうまく動作しなかったから。
echo $a で変数aを出力してくれない。
何故変数aを出力しないのか?
その原因は、readにあるのでは無くて、パイプ "|" にあるみたい。
パイプを使うと、パイプの両側にあるコマンドは、それぞれ別々にサブシェルで実行され、それをパイプで繋ぐと言うことらしい。
上の例で言うと、パイプを二つ使っているので、合計3つのサブシェルが生成され、それを二つのパイプが繋いでいるみたい。

こちらのサイト↓を見て漸く理解できた。

シェルスクリプト最大の罠、while問題 (1/2)

パイプの両側は、どちらも個別のサブシェルだ、と言うのが重要な点だと思う。
サブシェルで変数を代入しているから、親シェルや、パイプで繋がれた次のサブシェルでは変数の値が受け継がれなかったのだ。
という訳で最初のコードは、次の様にパイプの後側を1つのまとまりにしてやると、想定通り動きだす。

echo tama | (read a; echo $a;)
或いは
echo tama | { read a; echo $a; }

また、パイプの両側が親シェルとは別のサブシェルで実行されていると言うことは、次のコードで用意に確認出来るかも知れない。

exec echo tama | exec cat

execで現行のシェルを、それぞれechoやcatに置き換えているけど、execを実行しているのがサブシェルの為、親シェルが閉じられることは無い。


実はこれを理解するまで、ネット上をあちこち彷徨ってた…。
似た様な事は誰でも不思議に思っているみたいで、掲示板に誰かが質問し、誰かが答えている。
そんなのを見てると、リダイレクトを使った回答が結構あった。
で、見よう見まねで、リダイレクトを書いているうちに、リダイレクトの使い方も何となく分かってきた。

ヒアドキュメント(here document)で書くと…、
read a <<eof; echo $a;
tama
eof

リダイレクトは、コマンドの前に書いてもいいので…、
<<eof read a; echo $a;
tama
eof
とも書ける。個人的にはこっちの方が分りやすい。

bashなら、ヒアストリング(here string)で…、
<<<tama read a; echo $a;

或いは、プロセス置換(process substitution)からの入力を使って…、
< <(echo tama) read a; echo $a;
とも書ける。
最初に、< <(echo tama) みたいな構文を見た時、一体何をしてるのか?良く理解出来無かった。
しかしこれは、次の様なコマンド↓を実行すると理解できる。

ls <(echo tama)

↑の出力↓
/dev/fd/63

<(何かのコマンド) の実体は、ファイル(ファイルディスクリプタ)みたいだ。
そう言う訳で、< <(何かのコマンド) は、何かのコマンドの出力を標準入力へ読み込むと言う事が理解出来る。
(「プロセス置換は、名前付きパイプまたは、/dev/fd メソッドをサポートしているシステムでサポートされている。」みたいな事が何かに書いてあったので、他のシステムでは出力内容は若干異なるかも知れない。)

プロセス置換への出力を使って書くと…、
echo tama > >(read a; echo $a;)
とも書ける。
ただこれは、普通のコマンドなら、コマンドの出力後にプロンプトが表示されるのだが、上の場合コマンドプロンプトの表示後にコマンドの出力結果が表示される。
何でだろう?
ちなみにbashのバージョンは…、
4.3.30(1)-release (i586-pc-linux-gnu)


次に、これまでの事などを使って回りくどく書いていく。
入力を、ファイルディスクリプタ10番を使い迂回させ、標準入力へ入れる.

ヒアドキュメントからの入力…、
10<<eof <&10 read a; echo $a;
tama
eof
<&10 より先に 10<<eof を書かないと、「不正なファイル記述子です」と言われ怒られる。
<&10 が評価される時にはまだ、/dev/fd/10 が作られてないからだと思う。
10<<eof を書くことによって、/dev/fd/10 が作られるみたい。
最初はこの辺りが良く分からなかった。

ヒアストリングからの入力…、
10<<<tama <&10 read a; echo $a;
こっちの方がすっきりして分りやすい。

ついでに、プロセス置換も書くと…、
10< <(echo tama) <&10 read a; echo $a;

まだ今一つどう言う仕組みか、どう言う呼び方か分かって無いんだけど…、
ファイルディスクリプタを使った面白い書き方があった。
{hensu}<<eof <&${hensu} read a; echo $a;
tama
eof
bashだけなのか、他でも使えるのかも良く分から無い。
{hensu}のhensuは何でもいいんだけど、{hensu}< の様な感じでリダイレクトさせると、使用されてないファイルディスクリプタ10番からの数字を勝手にあてがって、ファイルディスクリプタを生成し、変数hensuにはその値が代入されている。
<&${hensu} の部分は、${hensu} でhensuの値を取り出して、ファイルディスクリプタ${hensu}番を標準入力へリダイレクトしている。
ls $hensu
或いは
ls ${hensu}
でも値を参照できる。


最後に、名前付きパイプで書くと…、
mkfifo paipudayo
echo tama >paipudayo &
read a <paipudayo
echo $a


もう一つだけ、今までのとは少しはずれるけど、面白い書き方があったのを思い出した。
ヒアドキュメントで書くと…、
. /dev/fd/0 <<eof
ls
eof

または…、
. /dev/stdin <<eof
ls
eof
入力をファイルディスクリプタから実行している。

ヒアストリングの方が分りやすいかもしれない。
. /dev/fd/0 <<<ls