Goで標準入力とファイル読み込みを可能にするインタフェース

kaneshin
Eureka Engineering
Published in
4 min readFeb 1, 2020

シェルではパイプを利用して標準入力から文字列を読み込み、標準出力へ出力することが多いですが、CLIではオプションや引数でファイル名を渡し、直接ファイルを読み込むこともあります。

Go言語でそのようなパターンをサポートするときに皆さんどうやって書いているのか気になったので、自分のテンプレを紹介します。

結論

意図はわかったからコード見せろという人向け👾

var r io.Reader
switch filename {
case "", "-":
r = os.Stdin
default:
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
r = f
}

コマンドで表現する

ひとまず、 file.dat という名前で適当な文字列が書き込まれているファイルを用意します。

$ cat file.dat
foobar

このファイルを /bin/reverse という文字列一行ずつ反転するコマンドを作ったとして、下記の3つのインタフェースを提供したい場合です。

$ cat file.dat | /bin/reverse
raboof
$ cat foobar | /bin/reverse -
raboof
$ /bin/reverse file.dat
raboof

CLIツールを作るときは POSIX の Utility Syntax Guidelines に沿ってコマンドを定義しているので、大体このようにいつも準備しています。

ダッシュ を使って標準入力から読み込ませるのはコマンドの意図に合わせてください。ダッシュはコマンドの利用のされ方では標準出力にもなり得るし、使用されないこともあります。詳しくは IEEE に仕様があるはずです。

コードで表現する

冒頭に書いたのと大差ないですが、このように記述しています。

var filename string
if args := flag.Args(); len(args) > 0 {
filename = args[0]
}
var r io.Reader
switch filename {
case "", "-":
r = os.Stdin
default:
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
r = f
}

読み込むようの変数を io.Reader で定義し、ファイル名が無いかダッシュだった場合は os.Stdin を設定、そうでない場合はファイルを読み込みます。

コマンドの引数が複数あるとかは各々カスタマイズしてください。けれど、大体これでいいんじゃないかと思っている。

コード全体

これは文字列をリバースするとかは実装していないです。ただ単に受け取った文字列を標準出力に出力しているだけです。

ちなみに、正常に標準入力かファイル名が渡ってこない場合は実行待ちになります。実行待ちとならないように工夫もできますが、パイプでコマンドを連鎖させるときは待ちや失敗のような異常系で止まることも重要なので、インタフェース違反が起きたら異常のまま処理をすすめます。

例えば、やりがちな例だとインタフェース違反をしたときにツール側では空文字列で処理をしてしまうなどをして正常に処理させるような対応はしていないです。(しないほうがいいです)

おわりに

ここらへんのインタフェースデザインは気になるのでみなさんの実装を見てみたいです😎

Utility Syntax Guidelines を読みたい方はこちらから

--

--

kaneshin
Eureka Engineering

Hi, I’m kaneshin. I’m currently working as a software engineer based in Tokyo, Japan.