kurumi-bioの雑記帳

プログラミング、パソコン、ペット、 犬、お出かけ

初心者のGo言語 -31- <Command>

こんにちは、kurumi-bioです。
第2回目のexecパッケージ(標準ライブラリー)の学習です

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.1 linux/amd64

Cmd型

type Cmd struct {
    // Path は、実行するコマンドのパスです。
    Path string
    // Args は、コマンドを Args[0] として含むコマンド ライン引数を保持します。
    Args []string
    // Env は、プロセスの環境を指定します。
    Env []string
    // Dir は、コマンドの作業ディレクトリを指定します。
    Dir string
    // Stdin は、プロセスの標準入力を指定します。
    Stdin io.Reader
    // Stdout と Stderr は、プロセスの標準出力とエラーを指定します。
    Stdout io.Writer
    Stderr io.Writer
    // ExtraFiles は、新しいプロセスによって継承される追加のオープン ファイルを指定します。
    ExtraFiles []*os.File
    // SysProcAttr は、オプションのオペレーティング システム固有の属性を保持します。
    SysProcAttr *syscall.SysProcAttr
    // Process は、いったん開始されると、基礎となるプロセスです。
    Process *os.Process
    // ProcessState には、終了したプロセスに関する情報が含まれています
    ProcessState *os.ProcessState
    // LookPath エラー (存在する場合)。
    Err error
    Cancel func() error
    WaitDelay time.Duration
}

type Cmdの説明 Cmd は、準備中または実行中の外部コマンドを表します。 Run、Output、または CombinedOutput メソッドを呼び出した後、Cmd を再利用することはできません。

Command関数

func Command(name string, arg ...string) *Cmd

変数の説明Command は、指定された引数で指定されたプログラムを実行する Cmd 構造体を返します。返された構造体に Path と Args のみを設定します。 name にパス セパレータが含まれていない場合、Command は LookPath を使用して、可能であれば name を完全なパスに解決します。それ以外の場合は、名前をパスとして直接使用します。 返された Cmd の Args フィールドは、コマンド名とそれに続く arg の要素から構成されるため、arg にコマンド名自体を含めることはできません。たとえば、Command("echo", "hello") です。 Args[0] は常に名前であり、解決される可能性のあるパスではありません。 Windows では、プロセスはコマンド ライン全体を 1 つの文字列として受け取り、独自の解析を行います。 Command は、CommandLineToArgvW (最も一般的な方法) を使用するアプリケーションと互換性のあるアルゴリズムを使用して、Args を組み合わせてコマンド ライン文字列に引用します。注目すべき例外は msiexec.exe と cmd.exe (したがって、すべてのバッチ ファイル) であり、引用符を外すアルゴリズムが異なります。これらのケースまたは他の同様のケースでは、自分で引用を行い、SysProcAttr.CmdLine で完全なコマンド ラインを指定して、Args を空のままにすることができます。

◆テストコード

package main

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

func main() {
    if len(os.Args) != 3 {
        os.Exit(1)
    }
    var sout, serr strings.Builder
    c := exec.Command(os.Args[1], os.Args[2])
    c.Stdout = &sout
    c.Stderr = &serr

    e := c.Run()

    if e != nil {
        if errors.Is(e, exec.ErrDot) {
            fmt.Fprintln(os.Stderr, "ErrorDot")
        } else if errors.Is(e, exec.ErrNotFound) {
            fmt.Fprintln(os.Stderr, "ErrNotFound")
        } else if errors.Is(e, exec.ErrWaitDelay) {
            fmt.Fprintln(os.Stderr, "ErrWaitDelay")
        }
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", e)
    }
    fmt.Printf("stdout=[%s]\n", sout.String())
    fmt.Printf("stderr=[%s]\n", serr.String())
}

コードの説明 指定された実行ファイルを指定した引数で実行し、stdoutstderrの内容を出力します。 Cmd.Run()関数の実行時にエラーが発生した場合は、エラー内容と該当するerrors.Isの値を表示します。

◆実行結果(Windows)

実行結果の説明 goコマンドをversion引数で実行しました。環境変数PATHが通っているのでgoコマンドが実行され標準出力(stdout)にバージョンが出力されました。

実行結果の説明 goコマンドに存在しない引数を指定して実行しました。goコマンドのエラーメッセージが標準エラー出力(stderr)に出力されました。

実行結果の説明 存在しないファイルを指定すると、executable file not found in %PATH%(%PATH%に実行可能ファイルが見つからない)というエラーが発生しました。

次に少し手を加えてexec.Command関数の戻り値Cmd構造体の内容を表示するプログラムを作成します。

◆テストコード

package main

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

func writeCmd(c exec.Cmd) {
    fmt.Printf("Path=[%s]\n", c.Path)
    for i, v := range c.Args {
        fmt.Printf("Args[%d]=[%s]\n", i, v)
    }
    p := c.Process
    if p != nil {
        if p != nil {
            fmt.Printf("pid=[%d]\n", c.Process.Pid)
        }
    }
}

func main() {
    if len(os.Args) != 3 {
        os.Exit(1)
    }
    var sout, serr strings.Builder

    c := exec.Command(os.Args[1], os.Args[2])
    c.Stdout = &sout
    c.Stderr = &serr

    e := c.Run()
    writeCmd(*c)

    if e != nil {
        fmt.Fprintf(os.Stderr, "ERROR=[%v]\n", e)
    }
    fmt.Printf("stdout=[%s]\n", sout.String())
    fmt.Printf("stderr=[%s]\n", serr.String())
}

コードの説明 exec.Cmd構造体の実行パス、実行時の引数、プロセスIDを出力します。 プロセスIDを出力するのでCmd.Run()関数でモジュール実行後に構造体の内容を出力しています。

◆実行結果(Windows)

実行結果の説明 goコマンドのパス、実行時の引数、プロセスIDが出力されました。

最後までご覧いただきありがとうございます