いろいろ備忘録日記

主に .NET とか Go とか Python絡みのメモを公開しています。

Goメモ-137 (exec.Command で シェルの展開機能 を利用する)

概要

よく忘れるのでメモメモ。

Go の os/exec パッケージには、コマンドを実行する exec.Command() がありますが、これを使用して

cmd = exec.Command("ls", "-l", "*.go")

みたいな実行をするとエラーになってしまいます。

シェルの展開を利用したい場合は、まずシェルを起動して、そこからシェルに実行してもらう必要があります。

cmd = exec.Command("/bin/bash", "-c", "ls -l *.go")

こんな感じ。

サンプル

package cmdexec

import (
    "bufio"
    "bytes"
    "errors"
    "os/exec"
    "runtime"

    "github.com/devlights/gomy/output"
)

// OneShot は、コマンドを一発実行して結果を取得するサンプルです.
//
// REFERENCES:
//   - https://stackoverflow.com/questions/19847594/how-to-reliably-detect-os-platform-in-go
//   - https://github.com/devlights/try-golang/issues/87
//   - https://stackoverflow.com/questions/31467153/golang-failed-exec-command-that-works-in-terminal
//   - https://github.com/github/hub/blob/2e002395b6a23fd2f51b9ed46e7d7581acd9dbd1/cmd/cmd.go#L40
func OneShot() error {
    if runtime.GOOS == "windows" {
        return errors.New("this example cannot run on Windows, sorry")
    }

    const (
        Shell = "/bin/bash"
    )

    var (
        cmd *exec.Cmd // コマンド
        out []byte    // 実行結果
        err error     // 実行時エラー
    )

    //
    // シェルの展開が必要ない場合は以下のようにそのまま指定して実行できる
    //

    out, _ = exec.Command("pwd").Output()
    output.Stdoutl("pwd", string(out))

    cmd = exec.Command("ls", "-l")
    out, err = cmd.Output()
    if err != nil {
        return err
    }

    output.Stdoutf("ls -l", "\n%s", string(out))
    output.StdoutHr()

    //
    // シェルの展開が必要な場合は sh -c または bash -c のようにシェル起動後にコマンド実行してもらう
    //

    cmd = exec.Command(Shell, "-c", "ls -l go.*")
    out, err = cmd.Output()
    if err != nil {
        return err
    }

    output.Stdoutf("ls -l go.*", "\n%s", string(out))
    output.StdoutHr()

    //
    // シェル起動後の実行は 普段のコマンド実行 と変わりない。パイプも指定できる
    //

    cmd = exec.Command(Shell, "-c", "ls -l | tail -n 3")
    out, err = cmd.Output()
    if err != nil {
        return err
    }

    output.Stdoutf("ls -l | tail -n 3", "\n%s", string(out))
    output.StderrHr()

    //
    // 結果は []byte で取得できているので、後で好きに加工できる
    //

    var (
        scanner  = bufio.NewScanner(bytes.NewReader(out))
        lastline string
    )

    for scanner.Scan() {
        lastline = scanner.Text()
    }

    if err := scanner.Err(); err != nil {
        return err
    }

    output.Stdoutl("last line", lastline)

    return nil
}

try-golang/oneshot.go at master · devlights/try-golang · GitHub

実行すると以下のようになります。

codespace ➜ /workspaces/try-golang (master) $ make run
go get -d ./...
go run -race github.com/devlights/try-golang/cmd/trygolang -onetime -example ""
ENTER EXAMPLE NAME: cmdexec_oneshot
[Name] "cmdexec_oneshot"
pwd                  /workspaces/try-golang

ls -l                
total 52
drwxrwxrwx+ 4 codespace root  4096 Mar 29 14:08 cmd
-rw-rw-rw-  1 codespace root   207 Mar 29 14:08 Dockerfile
-rw-rw-rw-  1 codespace root   810 Mar 29 14:08 go.mod
-rw-rw-rw-  1 codespace root 20341 Mar 29 14:08 go.sum
drwxrwxrwx+ 6 codespace root  4096 Mar 29 14:08 internal
-rw-rw-rw-  1 codespace root  1071 Mar 29 14:08 LICENSE
-rw-rw-rw-  1 codespace root  2147 Mar 29 14:08 Makefile
drwxrwxrwx+ 3 codespace root  4096 Mar 29 14:08 pkg
-rw-rw-rw-  1 codespace root  1466 Mar 29 14:08 README.md
-------------------------------------------------- 
ls -l go.*           
-rw-rw-rw- 1 codespace root   810 Mar 29 14:08 go.mod
-rw-rw-rw- 1 codespace root 20341 Mar 29 14:08 go.sum
-------------------------------------------------- 
ls -l | tail -n 3    
-rw-rw-rw-  1 codespace root  2147 Mar 29 14:08 Makefile
drwxrwxrwx+ 3 codespace root  4096 Mar 29 14:08 pkg
-rw-rw-rw-  1 codespace root  1466 Mar 29 14:08 README.md
-------------------------------------------------- 
last line            -rw-rw-rw-  1 codespace root  1466 Mar 29 14:08 README.md


[Elapsed] 10.816446ms

参考資料

stackoverflow.com

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

  • いろいろ備忘録日記まとめ

devlights.github.io

サンプルコードは、以下の場所で公開しています。

  • いろいろ備忘録日記サンプルソース置き場

github.com

github.com

github.com