理系学生日記

おまえはいつまで学生気分なのか

Golangでコマンドラッパーを作ってみる

業務でGolangを使うことにしたので、久しぶりにGolangを書いてみています。 シンタックスレベルでかなりの部分を忘れていて、もろもろググりながらという感じ。

最初のお題は、Golangを使って外部コマンドを実行するユーティリティでした。

実施したいこと

  • コマンドを実行できる
  • 実行結果でエラー判定ができる
  • コマンドの標準出力、標準エラー出力をコンソール出力できる
  • コマンドの標準出力、標準エラー出力をログファイルにも出力できる
  • コマンドの標準出力、標準エラー出力をStringとして取り出せる

実装

以下のような感じでとりあえず動く。 context周りをきれいにしていく

package main

import (
    "bytes"
    "context"
    "flag"
    "fmt"
    "io"
    "log"
    "os"
    "os/exec"
    "strings"
)

type CLI struct {
    out io.Writer // コマンドの標準出力の書き込み先
    err io.Writer // コマンドの標準エラー出力の書き込み先
}

func NewCLI(out, err io.Writer) *CLI {
    return &CLI{
        out: out,
        err: err,
    }
}

func (c *CLI) Exec(cmd string, args ...string) (stdout string, stdin string, err error) {
    cmdctx := exec.CommandContext(context.TODO(), cmd, args...)

    // 標準出力、標準エラー出力にはそのまま出力するとともに、byte.Bufferにも結果を蓄える
    var outbuf, errbuf bytes.Buffer
    cmdctx.Stdout = io.MultiWriter(c.out, &outbuf)
    cmdctx.Stderr = io.MultiWriter(c.err, &errbuf)

    log.Printf("$ %s %s", cmd, strings.Join(args, " "))
    if err := cmdctx.Run(); err != nil {
        return outbuf.String(), errbuf.String(), fmt.Errorf("executing %s %s: %w", cmd, strings.Join(args, ""), err)
    }
    return outbuf.String(), errbuf.String(), nil
}

func main() {
    flag.Parse()
    args := flag.Args()

    cli := NewCLI(os.Stdout, os.Stderr)
    stdout, stderr, err := cli.Exec(args[0], args[1:]...)
    if err != nil {
        log.Fatalf("error: %s", err)
    }

    fmt.Printf("stdout: %s\n", stdout)
    fmt.Printf("stderr: %s\n", stderr)
}

実行結果

標準出力

$ go run main.go echo hello world
2021/01/31 13:42:16 $ echo hello world
hello world
stdout: hello world

stderr:

標準エラー出力

$ cat stderr.sh
#!/bin/bash

echo "hello world" 1>&2
$ go run main.go ./stderr.sh
2021/01/31 13:44:11 $ ./stderr.sh
hello world
stdout:
stderr: hello world

コマンド実行エラー

$ go run main.go ls notExist
2021/01/31 13:45:07 $ ls notExist
ls: cannot access 'notExist': No such file or directory
2021/01/31 13:45:07 error: executing ls notExist: exit status 2
exit status 1