Golangで無名パイプからデータを受け取る方法【term.IsTerminalによる判定】
前書き:os.Args[1]にはパイプのデータがない
無名パイプは、Terminal上で用いる”|”の事です。例えば、以下の例ではechoコマンドがパイプで”PIPE test”を渡し、受け取り側のcatコマンドがパイプから受け取ったデータを標準出力しています。
1 2 |
$ echo "PIPE test" | cat PIPE test |
上記のcatコマンドのようにパイプからデータを受け取るには、「os.Args[1](コマンドライン引数の1つ目)を参照すれば良い」と私は勘違いしていました(正しくは標準入力からデータを受け取ります)。
本記事では、無名パイプ(”|”)からデータを受け取る実装例を紹介します。
無名パイプからデータを受け取る実装
以下に実装例と実行結果を示します。実装の詳細は、実行結果の後に後述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package main import ( "fmt" "io/ioutil" "os" "golang.org/x/term" ) func main() { if hasPipeData() { data, _ := fromPIPE() fmt.Print(data) } } func fromPIPE() (string, error) { if hasPipeData() { b, err := ioutil.ReadAll(os.Stdin) if err != nil { return "", err } return string(b), nil } return "", nil } func hasPipeData() bool { return !term.IsTerminal(syscall.Stdin) } |
実行例は以下の通りです。
1 2 3 4 5 6 7 |
$ go build -o pipe main.go $ echo "PIPE test" | ./pipe PIPE test $ ./pipe not_pipe (注釈) 何も表示されない |
実装説明:hasPipeData() 部分
golang.org/x/termのterminal.IsTerminal()は、引数で渡されたファイルディスクリプタ(今回は標準入力、fd=0)がターミナルからの入力かどうかを返します。以前は、golang.org/x/crypto/ssh/terminalのterminal.IsTerminal()で確認していたようですが、deprecated(非推奨)となっています。
1 2 3 |
func hasPipeData() bool { return !term.IsTerminal(syscall.Stdin) } |
標準入力がターミナルかどうかを確認する理由は、下図の通りです。
パイプを用いて実行したかどうかによって、プロセスの標準入力(STDIN)がターミナルもしくはパイプ(他プロセスの標準出力)のどちらに結びつくのかが異なります。標準入力がターミナルと結びついていなければ、パイプからデータがあるとみなせます。
実装説明:fromPIPE() 部分
パイプからのデータは、標準入力を読み込む事によって取得できます。
1 2 3 4 5 6 7 8 9 10 |
func fromPIPE() (string, error) { if hasPipeData() { b, err := ioutil.ReadAll(os.Stdin) if err != nil { return "", err } return string(b), nil } return "", nil } |
os.Args(プロセス実行時の引数)からパイプのデータが取得できなかった理由は、
- シェル(例:Bash)は、プロセスの標準入力を別プロセスの標準出力と紐付け
- シェルは、os.Args(コマンドライン引数)にパイプからのデータを追加しない
という挙動だからでしょう。
昔読んだ「Xinuオペレーティングシステムデザイン 改訂2版」のシェルを思い返すと、シェルがターミナルから渡された文字列をスタックにセットする事によって、プロセスに対してコマンドライン引数を渡していました。この挙動は、上記の1.〜2とほぼ一致します(補足:Xinuにパイプの仕組みはありません)。
後書き
GolangでBusyBoxのパクリを実装しています。その中で、「パイプからデータを受け取る処理」が登場したわけですが、実装してみて初めてパイプを正しく理解できた気がします。
ロシア人と国際結婚した地方エンジニア。
小学〜大学院、就職の全てが新潟。
大学の専攻は福祉工学だったのに、エンジニアとして就職。新卒入社した会社ではOS開発や半導体露光装置ソフトを開発。現在はサーバーサイドエンジニアとして修行中。HR/HM(メタル)とロシア妻が好き。サイトに関するお問い合わせやTwitterフォローは、お気軽にどうぞ。
1件の返信
[…] 全コマンドに影響がある修正は、何度も発生しました。例えば、「パイプ(”|”)対応」「リダイレクト(”>”や”>>”)対応」「引数で指定された環境変数の展開」あたりは、初期実装時には未対応でした。そのため、途中で機能追加しました。が、テストが無かったのでデグレしているのかが即座に分からず、手間が多くかかりました。 […]