😀
Go言語でのターミナル入力とパイプ入力
概要
CLIを作る時に、ターミナルからのインプットとPipe処理からのインプットの区別が必要になったのでまとめる
目次
- ソースコード
- 実行結果
- 原理と実装詳細
- 参考文献
ソースコード
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からの入力かを区別した。
参考文献
- [プロセス・パイプ・リダイレクション・ファイルディスクリプタの実体を見に行く] (https://zariganitosh.hatenablog.jp/entry/20130722/process_pape_redirect_file_descriptor)
- osパッケージ
- terminalパッケージ
Discussion