Golangで無名パイプからデータを受け取る方法【term.IsTerminalによる判定】

前書き:os.Args[1]にはパイプのデータがない

無名パイプは、Terminal上で用いる”|”の事です。例えば、以下の例ではechoコマンドがパイプで”PIPE test”を渡し、受け取り側のcatコマンドがパイプから受け取ったデータを標準出力しています。

上記のcatコマンドのようにパイプからデータを受け取るには、「os.Args[1](コマンドライン引数の1つ目)を参照すれば良い」と私は勘違いしていました(正しくは標準入力からデータを受け取ります)。

本記事では、無名パイプ(”|”)からデータを受け取る実装例を紹介します。

                       

無名パイプからデータを受け取る実装

以下に実装例と実行結果を示します。実装の詳細は、実行結果の後に後述します。

実行例は以下の通りです。

                 

実装説明:hasPipeData() 部分

golang.org/x/termのterminal.IsTerminal()は、引数で渡されたファイルディスクリプタ(今回は標準入力、fd=0)がターミナルからの入力かどうかを返します。以前は、golang.org/x/crypto/ssh/terminalのterminal.IsTerminal()で確認していたようですが、deprecated(非推奨)となっています。

標準入力がターミナルかどうかを確認する理由は、下図の通りです。

パイプを用いて実行したかどうかによって、プロセスの標準入力(STDIN)がターミナルもしくはパイプ(他プロセスの標準出力)のどちらに結びつくのかが異なります。標準入力がターミナルと結びついていなければ、パイプからデータがあるとみなせます。

                

実装説明:fromPIPE() 部分

パイプからのデータは、標準入力を読み込む事によって取得できます。

os.Args(プロセス実行時の引数)からパイプのデータが取得できなかった理由は、

  1. シェル(例:Bash)は、プロセスの標準入力を別プロセスの標準出力と紐付け
  2. シェルは、os.Args(コマンドライン引数)にパイプからのデータを追加しない

という挙動だからでしょう。

昔読んだ「Xinuオペレーティングシステムデザイン 改訂2版」のシェルを思い返すと、シェルがターミナルから渡された文字列をスタックにセットする事によって、プロセスに対してコマンドライン引数を渡していました。この挙動は、上記の1.〜2とほぼ一致します(補足:Xinuにパイプの仕組みはありません)。

               

後書き

GolangでBusyBoxパクリを実装しています。その中で、「パイプからデータを受け取る処理」が登場したわけですが、実装してみて初めてパイプを正しく理解できた気がします。

 

おすすめ

1件の返信

  1. 2021年11月28日

    […] 全コマンドに影響がある修正は、何度も発生しました。例えば、「パイプ(”|”)対応」「リダイレクト(”>”や”>>”)対応」「引数で指定された環境変数の展開」あたりは、初期実装時には未対応でした。そのため、途中で機能追加しました。が、テストが無かったのでデグレしているのかが即座に分からず、手間が多くかかりました。 […]