いろいろ備忘録日記

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

Goメモ-492 (プロセスの結果コードを扱う)(exec.Command, exec.ExitError.ExitCode)

関連記事

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

Goの中から、別のプロセスを起動して、その結果コードを受け取りたいことがたまにあります。

大した内容では無いのですが、やり方をいつも忘れるので、ここにメモメモ。。。

サンプル

起動するプロセスは何でも良いのですが、以下のようなドウでも良いプログラムを用意。

main.c

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    int i = atoi(argv[argc-1]);
    if (i >= 5) {
        exit(99-i);
    }
    
    if (i%2 == 0) {
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

C側をビルドするためのタスクファイルです。

Taskfile.yml (C)

# https://taskfile.dev

version: '3'

vars:
  C_APP: capp

tasks:
  default:
    cmds:
      - task: run
  build:
    cmds:
      - gcc -o ../{{.C_APP}}{{exeExt}} main.c

利用するGo側のコード。

main.go

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os"
    "os/exec"
    "strconv"
    "time"
)

func main() {
    var (
        rootCtx          = context.Background()
        mainCtx, mainCxl = context.WithTimeout(rootCtx, 1*time.Second)
    )
    defer mainCxl()

    if err := run(mainCtx); err != nil {
        log.Fatal(err)
    }
}

func run(ctx context.Context) error {
    for i := range 10 {
        var (
            ctx, cxl = context.WithTimeout(ctx, 100*time.Millisecond)
            program  = os.Args[1]
            param    = strconv.Itoa(i)
            cmd      = exec.CommandContext(ctx, program, param)

            err      error
            exitCode int
            success  = true
        )
        defer cxl()

        if err = cmd.Run(); err != nil {
            //
            // 結果コードが 0、つまり、成功の場合は err は nil となる。
            //
            // 0以外の場合、errとして返ってくる。
            // この場合、err は *exec.ExitError となっているので
            // errors.As() で、変換できるか確認し、ExitCode() で取得する。
            //
            // Run() している最中に context がタイムアウトした場合
            // 結果コードは -1 となって返ってくる。
            //
            // Run() する前に context がタイムアウトした場合
            // context.DeadlineExceeded が返ってくる。
            //
            var exitErr *exec.ExitError
            if errors.As(err, &exitErr) {
                exitCode = exitErr.ExitCode()
                success = exitErr.Success()
            } else {
                // 別のエラーの場合 (context.DeadlineExceeded など)
                return err
            }
        }

        fmt.Printf("実行引数: %d\t成功: %v\t結果コード: %d\n", i, success, exitCode)
    }

    return nil
}

Go側をビルドして実行するタスクファイルです。

Taskfile.yml (Go)

# https://taskfile.dev

version: '3'

vars:
  C_APP: capp
  GO_APP: goapp

includes:
  C:
    taskfile: ./c/Taskfile.yml
    dir: ./c

tasks:
  default:
    cmds:
      - task: run
  build:
    cmds:
      - task: C:build
      - go build -o {{.GO_APP}}{{exeExt}} main.go
  run:
    deps: [ build ]
    cmds:
      - ./{{.GO_APP}}{{exeExt}} "./{{.C_APP}}{{exeExt}}"
  clean:
    cmds:
      - rm -f ./{{.C_APP}}{{exeExt}} ./{{.GO_APP}}{{exeExt}}

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

shell

$ task
task: [C:build] gcc -o ../capp main.c
task: [build] go build -o goapp main.go
task: [run] ./goapp "./capp"
実行引数: 0     成功: false     結果コード: 1
実行引数: 1     成功: true      結果コード: 0
実行引数: 2     成功: false     結果コード: 1
実行引数: 3     成功: true      結果コード: 0
実行引数: 4     成功: false     結果コード: 1
実行引数: 5     成功: false     結果コード: 94
実行引数: 6     成功: false     結果コード: 93
実行引数: 7     成功: false     結果コード: 92
実行引数: 8     成功: false     結果コード: 91
実行引数: 9     成功: false     結果コード: 90

try-golang/examples/singleapp/exit_code at main · devlights/try-golang · GitHub

参考情報

Goのおすすめ書籍


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

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