Goで対話型コマンドを作成する

[Fusic Advent Calendar 2017](https://qiita.com/advent-calendar/2017/fusic) 18日目
[Go2 Advent Calendar 2017] (https://qiita.com/advent-calendar/2017/go2) 18日目の記事です。

私はAWSをよく触るのですが、そのコマンドラインツールのラッパーを作成したくなりました。

普段なら`bash`でチャチャッと作ってしまうのですが、ずっと使いたいと思っていた`Go`を無理やり使ってみようと思います。
（Go初心者です。ご容赦ください）

# やること

以下を簡単に行いたいと考えました。

- S3内のバケット一覧を表示
- バケットを選択させる
- 選択されたバケットの一覧が出て来る
- オブジェクトを選択させる
- オブジェクトを取得する

## コード
```go:easy_aws
package main

import (
    "fmt"
    "os"
    "os/exec"
    "strings"
)

func main() {

    // aws s3 ls コマンドの結果を格納
    out, err := exec.Command("aws", "s3", "ls").Output()

    // Error時は終了
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // 出力を改行分割
    ret := strings.Split(string(out), "\n")

    fmt.Print("何番のバケットの中身を出力しますか？ \n")

    // Bucketを slice に格納
    var Buckets []string
    Buckets = s3StdinSlice(ret)

    // ユーザ入力にてバケットを選択させる
    var i int
    fmt.Scan(&i)

    // 選択させたバケットの特定
    var TargetBuckets string
    TargetBuckets = Buckets[i]

    out, err = exec.Command("aws", "s3", "ls", TargetBuckets).Output()

    // Error時は終了
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // 出力を改行分割
    ret = strings.Split(string(out), "\n")

    // 取得Error時は終了
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    // Objectを slice に格納
    var Objs []string

    Objs = s3StdinSlice(ret)

    fmt.Print("どのオブジェクトを取得しますか？")
    // ユーザ入力にてオブジェクトを選択させる
    fmt.Scan(&i)

    // 選択させたオブジェクトの特定
    var TargetObj string
    TargetObj = Objs[i]

    fmt.Printf("次のオブジェクトを取得中です。暫くお待ちください\n%s", TargetObj)
    out, err = exec.Command("aws", "s3", "cp", "s3://"+TargetBuckets+"/"+TargetObj, ".").Output()
    // 取得Error時は終了
    if err != nil {
        fmt.Println(err.Error())
        os.Exit(1)
    }

    fmt.Printf("aws cp result: \n%s", string(out))
```

```go:s3StdinSlice
func s3StdinSlice(stdin []string) []string {
    var retList []string
    for index, line := range stdin {
        if line != "" {
            // 空白で区切り最後の要素を取り出す
            Bucket := strings.Split(line, " ")
            retList = append(retList, Bucket[len(Bucket)-1])
            fmt.Printf("%d %s \n", index, Bucket[len(Bucket)-1])
        }
    }
    return retList
}
```
## 実行
```zsh
seike460% go run easy_aws.go                                                                                                                                                                                                                                    [~/go/easy_aws]
何番のバケットの中身を出力しますか？
0 seike460
1 seike460-ses
1　　←ユーザ入力部分
0 5537ijfodp64nn66gpudgjko3btpjl1bde1h6b81
1 8vpnn3vgk6pmp30toc9o8lhk6f9b1v96tmgj23o1
2 AMAZON_SES_SETUP_NOTIFICATION
3 at04ee7ef79ond4rseo6mnpdss368afirn75so01
4 c0vaf78egmmkjlj5k7k6m8k45r7qro5m9t9s7v81
5 em6ogav7i9dcp565pn2hmb4f9alsj104cr45gpg1
6 f2bnmk946m5jvsjgjtg69ld50bho3j2hm2f0sc01
7 htb2dmv65e6gvo6f2ea64pu37nebtso98hecpsg1
8 lmi97n5v486c1ch7c1rup435mhajdcqp1ur2c601
9 mcfckv8plg02a03k7pkbeqfiu4874fvi453ddq01
10 n603hm0obfte31kmqqhamvu08micd20hd1csjto1
11 nbek230ea448rksopdmtltkce68f6t0rf37luo81
12 nn1riqakvlgja9bjfipek14sts76o8e539dsnt81
13 rg7or6p1sj4n58vt8a6pibgeamqdekcu8c3pfa81
14 t8u418e6691kl8cmc0p74291hlm0sgqhukhb5301
15 u65lcdtu6dafemrdh66blu3psc160bl6rpuurko1
16 v0mddrch2844700lsbbq0ha833spnlh987r6cm01
どのオブジェクトを取得しますか？
15　　←ユーザ入力部分
次のオブジェクトを取得中です。暫くお待ちください
u65lcdtu6dafemrdh66blu3psc160bl6rpuurko1aws cp result:
download: s3://seike460-ses/u65lcdtu6dafemrdh66blu3psc160bl6rpuurko1 to ./u65lcdtu6dafemrdh66blu3psc160bl6rpuurko1
```

予定通り取得できました。(入力バリデーションが出来ていなかったり、ディレクトリの時の対応ができていなかったりします。)
※　exec を使ってしまっていますが、間違いなくAWS SDKを使ったほうが良いでしょう。
https://aws.amazon.com/jp/sdk-for-go/
準備出来次第アップデートします。

##　利用した標準パッケージ

[fmt](https://golang.org/pkg/fmt/)
[os](https://golang.org/pkg/os/)
[exec](https://golang.org/pkg/os/exec/)

## まとめ
やろうとした事が簡単な内容だったのと思いの外サクッと出来てしまったので、少し不完全燃焼感があります。。。
とはいえ作成したバイナリを配れるという利点がありますので、
今作ろうとしているコマンド、試しにGoで作ってみては如何でしょうか？

## おまけ
ユーザ入力読み込みは以下の様にStringで取得する事も可能です。
こっちでやると、入力が面倒なので数字で選択出来る様にしました。

`bufio`のimportが必要です

```go:bufio
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("どのバケットの中身を出力しますか？")
    OutputBucket, err1 := reader.ReadString('\n')
```