業務で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