😀

Go言語でのターミナル入力とパイプ入力

2024/03/09に公開

概要

CLIを作る時に、ターミナルからのインプットとPipe処理からのインプットの区別が必要になったのでまとめる

目次

  1. ソースコード
  2. 実行結果
  3. 原理と実装詳細
  4. 参考文献

ソースコード

main.go
package main

import (
	"fmt"
	"io/ioutil"
	"os"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	TERMINAL = "terminal"
	PIPE     = "pipe"
)

func main() {
	isTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
	switch isTerminal {
	case true:
		var stdin string
		fmt.Scan(&stdin)
		fmt.Printf("type: %s, text: %s\n", TERMINAL, stdin)
		break
	case false:
		stdin, _ := ioutil.ReadAll(os.Stdin)
		fmt.Printf("type: %s, text: %s", PIPE, string(stdin))
		break
	default:
	}
}

実行結果

実際に入力して区別できているか確かめる

ターミナル入力の場合

キーボードからのstdinで hoge という文字を入力する

$: go run .\main.go
hoge 
type: terminal, text: hoge

pipe入力の場合

pipeからのstdinで hoge という文字を入力する

$: echo hoge | go run .\main.go
type: pipe, text: hoge

原理と実装詳細

なぜ上の実装を行ったか書く。

パイプ処理ではos.Stdinがターミナル入力ではない

golangの実行ファイルを実行すると、OSはそれを実行するためのプロセスを立ち上げる。この時、stdin(/dev/stdin)はデフォルトではキーボードからの入力を受け付けているので、stdinはターミナルからの入力として扱われる。一方、golangの実行ファイルが他のプロセスからのパイプ処理として実行されると、そのgolangのプロセスにおけるstdin(/dev/stdin)はキーボードではなく、別のプロセス(ターミナルで見ると|の左側のコマンドのためのプロセス)からの出力を受け付ける。つまり、キーボードからの入力ではなくなっている。そして、golangのos.Stdinは/dev/stdinがfile descriptorとして登録されている。

terminal#IsTerminalを使って、指定したfile descriptorがターミナルからの入力か判断する

terminalパッケージのIsTerminalを引数にfile desctiptorを渡して呼ぶと、それがキーボードからの入力かどうかを判断してくれる。そこで、今回はstdinのfile descriptorを表すos.Stdin.Fd()を引数にして呼んだ。これによって、ターミナルからの入力かpipeからの入力かを区別した。

参考文献

Discussion