kurumi-bioの雑記帳

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

初心者のGo言語 -40- <LookupGroup,LookupGroupId,Current,Lookup,LookupId,GroupIds>

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

前回の記事

kurumi-bio.hatenablog.com

APIリファレンス(過去記事の一覧)

kurumi-bio.hatenablog.com

環境

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

LookupGroup関数

func LookupGroup(name string) (*Group, error)

関数の説明 LookupGroup はグループを名前で検索します。グループが見つからない場合、返されるエラーのタイプは UnknownGroupError です。

package main

import (
    "fmt"
    "os"
    "os/user"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: LookupGroup グループ名")
        os.Exit(1)
    }
    g, e := user.LookupGroup(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "LookupGroup Error:%v\n", e)
        os.Exit(2)
    }
    fmt.Printf("Name=[%s]\n", g.Name)
    fmt.Printf("Gid=[%s]\n", g.Gid)
}

コードの説明 実行時の引数で渡された"グループの名前"をuser.LookupGroup関数で検索し、その結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windowsは、"Administrators"、"Power Users"、"Guests"のグループ名を検索し、 Linuxは、"root"と"Users"のグループ名を検索して、それぞれのGidが出力されました。

◆実行結果(Linux)

実行結果の説明 存在しないグループ名を検索すると group: unknown group <グループ名>(未知のグループ)と出力されました。

LookupGroupId関数

func LookupGroupId(gid string) (*Group, error)

関数の説明 LookupGroupId は、groupid によってグループを検索します。グループが見つからない場合、返されるエラーのタイプは UnknownGroupIdError です。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/user"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: LookupGroupId グループID")
        os.Exit(1)
    }
    g, e := user.LookupGroupId(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "LookupGroupId Error:%v\n", e)
        os.Exit(2)
    }
    fmt.Printf("Name=[%s]\n", g.Name)
    fmt.Printf("Gid=[%s]\n", g.Gid)
}

コードの説明 実行時の引数で渡された"グループのID"をuser.LookupGroupId関数で検索し、その結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 user.LookupGroup関数で出力されたグループIDを検索し、それぞれグループ名が出力されました。

◆実行結果(Linux)

実行結果の説明 存在しないグループIDを検索するとgroup: unknown groupid <グループId>(未知のグループId)と出力されました。

GroupIds関数

func (u *User) GroupIds() ([]string, error)

関数の説明 GroupIds は、ユーザーがメンバーであるグループ ID のリストを返します。

Current関数

func Current() (*User, error)

関数の説明 Current は現在のユーザーを返します。 最初の呼び出しでは、現在のユーザー情報がキャッシュされます。後続の呼び出しではキャッシュされた値が返され、現在のユーザーへの変更は反映されません。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/user"
)

func getGroupName(gid string) string {
    n, e := user.LookupGroupId(gid)
    if e != nil {
        fmt.Fprintf(os.Stderr, "LookupGroupId Error[%s]:%v\n", gid, e)
        return ""
    }
    return n.Name
}

func main() {
    u, e := user.Current()
    if e != nil {
        fmt.Fprintf(os.Stderr, "Current Error:%v\n", e)
        os.Exit(1)
    }
    fmt.Printf("Gid=[%s]:[%s]\n", u.Gid, getGroupName(u.Gid))
    fmt.Printf("HomeDir=[%s]\n", u.HomeDir)
    fmt.Printf("Name=[%s]\n", u.Name)
    fmt.Printf("Uid=[%s]\n", u.Uid)
    fmt.Printf("Username=[%s]\n", u.Username)
    l, e2 := u.GroupIds()
    if e2 != nil {
        fmt.Fprintf(os.Stderr, "GroupIds Error:%v\n", e2)
        os.Exit(2)
    }
    for c, s := range l {
        fmt.Printf("GroupId[%d]=[%s]:[%s]\n", c, s, getGroupName(s))
    }
}

コードの説明 現在のユーザー(ログインユーザー)の情報を出力します。
ユーザーが複数のグループに所属している可能性があるので、GroupIds関数でグループIDのリストを取得して出力しています。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 ユーザー情報が出力されました。
Windowsは、ユーザー名にホスト名が含まれていました。
また、二つのグループに所属していて、片方はグループ名が"なし"になっていました。
プロパティを表示すると所属するグループは"Users"のみでした。謎です。

Lookup関数

func Lookup(username string) (*User, error)

関数の説明 ルックアップはユーザー名でユーザーを検索します。ユーザーが見つからない場合、返されるエラーのタイプは UnknownUserError です。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/user"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: Lookup ユーザー名")
        os.Exit(1)
    }
    u, e := user.Lookup(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "Lookup Error:%v\n", e)
        os.Exit(2)
    }
    fmt.Printf("Gid=[%s]\n", u.Gid)
    fmt.Printf("HomeDir=[%s]\n", u.HomeDir)
    fmt.Printf("Name=[%s]\n", u.Name)
    fmt.Printf("Uid=[%s]\n", u.Uid)
    fmt.Printf("Username=[%s]\n", u.Username)
    l, e2 := u.GroupIds()
    if e2 != nil {
        fmt.Fprintf(os.Stderr, "GroupIds Error:%v\n", e2)
        os.Exit(3)
    }
    for c, s := range l {
        fmt.Printf("GroupId[%d]=[%s]\n", c, s)
    }
}

コードの説明 実行時の引数で渡された"ユーザーの名前"をuser.Lookup関数で検索し、その結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 起動時の引数で指定したユーザー名の情報が出力されました。
Windowsでは、ホスト名を省いてユーザー名のみでも結果が出力されました。

◆実行結果(Linux)

実行結果の説明 存在しないユーザー名を検索するとgroup: unknown user <ユーザー名>(未知のユーザー名)と出力されました。

LookupId関数

func LookupId(uid string) (*User, error)

関数の説明 LookupId はユーザー ID によってユーザーを検索します。ユーザーが見つからない場合、返されるエラーのタイプは UnknownUserIdError です。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/user"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage: LookupId ユーザーID")
        os.Exit(1)
    }
    u, e := user.LookupId(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "LookupId Error:%v\n", e)
        os.Exit(2)
    }
    fmt.Printf("Gid=[%s]\n", u.Gid)
    fmt.Printf("HomeDir=[%s]\n", u.HomeDir)
    fmt.Printf("Name=[%s]\n", u.Name)
    fmt.Printf("Uid=[%s]\n", u.Uid)
    fmt.Printf("Username=[%s]\n", u.Username)
    l, e2 := u.GroupIds()
    if e2 != nil {
        fmt.Fprintf(os.Stderr, "GroupIds Error:%v\n", e2)
        os.Exit(3)
    }
    for c, s := range l {
        fmt.Printf("GroupId[%d]=[%s]\n", c, s)
    }
}

コードの説明 実行時の引数で渡された"ユーザーID"をuser.LookupId関数で検索し、その結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 指定したユーザーIDの情報が出力されました。

◆実行結果(Linux)

実行結果の説明 存在しないユーザーIDを検索するとgroup: unknown userid <ユーザーID>(未知のユーザーid)と出力されました。

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

初心者のGo言語 -39- <NotifyContext,Reset,Stop>

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

前回の記事

kurumi-bio.hatenablog.com

APIリファレンス(過去記事の一覧)

kurumi-bio.hatenablog.com

環境

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

NotifyContext関数

func NotifyContext(parent context.Context, signals ...os.Signal) (ctx context.Context, stop context.CancelFunc)

関数の説明 NotifyContext は、リストされたシグナルの 1 つが到着したとき、返された stop 関数が呼び出されたとき、または親コンテキストの Done チャネルが閉じられたときのいずれかが先に起こったときに、完了とマークされた (その Done チャネルが閉じられている) 親コンテキストのコピーを返します。

stop 関数は信号の動作の登録を解除し、signal.Reset と同様に、特定の信号のデフォルトの動作を復元する場合があります。たとえば、os.Interrupt を受信した Go プログラムのデフォルトの動作は終了します。 NotifyContext(parent, os.Interrupt) を呼び出すと、返されたコンテキストをキャンセルするように動作が変更されます。今後受信した割り込みは、返された停止関数が呼び出されるまで、デフォルト (終了) 動作をトリガーしません。

stop 関数はそれに関連付けられたリソースを解放するため、このコンテキストで実行中の操作が完了し、シグナルをコンテキストに転送する必要がなくなったら、コードはすぐに stop を呼び出す必要があります。

◆テストコード

package main

import (
    "context"
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    var ctx context.Context
    var stop context.CancelFunc

    fmt.Printf("pid=[%d]\n", os.Getpid())

    if len(os.Args) == 1 {
        ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGTERM)
    } else {
        ctx, stop = signal.NotifyContext(context.Background(), syscall.SIGINT)
    }
    defer stop()

    select {
    case <-time.After(10 * time.Second):
        fmt.Println("missed signal")
    case <-ctx.Done():
        fmt.Println(ctx.Err())
        stop()
    }
}

コードの説明 実行時の引数の有無で受けるシグナルを変えています。
引数が無い場合はsyscall.SIGTERMで、引数が有る場合はsyscall.SIGINTが到着するとコンテキストがキャンセルされます。

◆実行結果(Linux)

実行結果の説明 1回目は、引数無しで実行しているので、syscall.SIGTERMの到着を待っているためCtrl+Cを押しても コンテキストはキャンセルされませんでした。
2回目は、引数有で実行しているので、syscall.SIGINTの到着を待っており、Ctrtl+Cを押すとコンテキストがキャンセルされました。

Reset関数

func Reset(sig ...os.Signal)

関数の説明 Reset は、提供されたシグナルに対する以前の Notify 呼び出しの効果を取り消します。シグナルが提供されない場合、すべてのシグナル ハンドラーがリセットされます。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    fmt.Printf("pid=[%d]\n", os.Getpid())

    signal.Ignore(syscall.SIGINT)

    c := make(chan os.Signal, 1)

    signal.Notify(c)
    s := <-c
    fmt.Printf("signal=[%s]\n", s)

    if len(os.Args) == 1 {
        signal.Reset(syscall.SIGINT)
    }
    s = <-c
    fmt.Printf("signal=[%s]\n", s)
}

コードの説明 syscall.SIGINTを無効にした状態で、シグナルの通知を2回受信して表示しています。
コマンド実行時の引数が無い場合は、2回目の受信時はsyscall.SIGINTを取り消し(signal.Reset)てます。

◆実行結果(Linux)

実行結果の説明 1回目の実行は引数無しなので、Ctrl+Cの受信が取り消されているためCtrl+Zを押すまで2回目のシグナルの通知を受信しませんでした。
2回目の実行は引数有なので、受信の取り消しが行われなかったため、Crtl+Cの通知を2回受信し処理が終了しました。

Stop関数

func Stop(c chan<- os.Signal)

関数の説明 Stop により、パッケージ信号が c への受信信号の中継を停止します。これにより、c を使用した以前の Notify 呼び出しの効果がすべて取り消されます。 Stop が返されると、c はそれ以上信号を受信しないことが保証されます。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    fmt.Printf("pid=[%d]\n", os.Getpid())

    signal.Ignore(syscall.SIGINT)

    c := make(chan os.Signal, 1)

    signal.Notify(c)
    s := <-c
    fmt.Printf("signal=[%s]\n", s)

    if len(os.Args) == 1 {
        signal.Stop(c)
    }
    s = <-c
    fmt.Printf("signal=[%s]\n", s)
}

コードの説明 syscall.SIGINTを無効にした状態で、シグナルの通知を2回受信して表示しています。
コマンド実行時の引数が無い場合は、1回目の受信の後に信号の中継を停止(signal.Stop)しています。

◆実行結果(Linux)

実行結果の説明 1回目の実行は引数無しなので、1回目の受信後に信号の中継を停止したため、
2回目が受信できず処理が終わらなかったので、killコマンドで終了(Terminated)しました。
2回目の実行は引数有なので、信号の中継が停止されないため、シグナルの通知を2回受信し処理が終了しました。  

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

初心者のGo言語 -38- <Ignore,Ignored,Notify>

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

環境

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

Ignore関数

func Ignore(sig ...os.Signal)

関数の説明 Ignoreは、提供されたシグナルが無視されます。 プログラムがそれらを受信して​​も、何も起こりません。 Ignore は、提供されたシグナルに対する以前の Notify 呼び出しの効果を取り消します。信号が提供されない場合、すべての受信信号は無視されます。

Ignored関数

func Ignored(sig os.Signal) bool

関数の説明 Ignored は、sig が現在無視されているかどうかを報告します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    fmt.Printf("pid=[%d]\n", os.Getpid())
    signal.Ignore(syscall.SIGINT, syscall.SIGTERM)
    fmt.Println("Ignored Start")
    fmt.Printf("syscall.SIGKILL=[%v]\n", signal.Ignored(syscall.SIGKILL))
    fmt.Printf("syscall.SIGINT=[%v]\n", signal.Ignored(syscall.SIGINT))
    fmt.Printf("syscall.SIGTERM=[%v]\n", signal.Ignored(syscall.SIGTERM))
    time.Sleep(10 * time.Second)
    fmt.Println("end")
}

コードの説明 実行するとpidを表示し、signal.Ignore()関数を使ってsyscall.SIGINTとsyscall.SIGTERMのシグナルを10秒間無視します。
また、signal.Ignored()関数を使ってsyscall.SIGKILL、syscall.SIGINT、syscall.SIGTERMが無視されているか表示します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明

  • Windows
    Ctrl+cを押すとプロセスが終了しました。
    Windowsは、signal.Ignored()関数が対応していないと思われます。

  • Linux
    Ctrl+cを押しても、kill <プロセスid>を実行してもプロセスが終了しませんでした。

Notify関数

func Notify(c chan<- os.Signal, sig ...os.Signal)

関数の説明 Notify により、パッケージ信号は受信信号を c に中継します。信号が提供されない場合、すべての受信信号は c に中継されます。それ以外の場合は、提供された信号のみが表示されます。

パッケージ信号は c への送信をブロックしません。呼び出し元は、期待される信号速度に対応するのに十分なバッファー領域が c にあることを確認する必要があります。 1 つの信号値のみの通知に使用されるチャネルの場合、サイズ 1 のバッファで十分です。

同じチャネルで Notify を複数回呼び出すことができます。呼び出しごとに、そのチャネルに送信される信号のセットが拡張されます。セットから信号を削除する唯一の方法は、Stop を呼び出すことです。

異なるチャネルと同じ信号を使用して Notify を複数回呼び出すことができます。各チャネルは着信信号のコピーを個別に受信します。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    signal.Ignore(syscall.SIGINT, syscall.SIGTERM)

    fmt.Printf("pid=[%d]\n", os.Getpid())
    for {
        c := make(chan os.Signal, 1)
        signal.Notify(c)
        s := <-c
        fmt.Printf("signal=[%s]\n", s)
    }
}

コードの説明 signal.Ignore()関数を使ってsyscall.SIGINTとsyscall.SIGTERMのシグナルを無視します。 無限ループで、signal.Notify()関数を使って受け取ったシグナルを表示します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明

  • Windows
    Ctrl+cを押すと"interrup"のシグナルを受信しました。
    プロセスが停止しなかったのでタスクマネージャで強制終了しました。

  • Linux
    Ctrl+cを押すと"interrupt"のシグナルを受信
    Ctrl+zを押すと"stopped"のシグナルを受信
    kill <プロセスId>で、"terminated"のシグナルを受信
    Kill -9 <プロセスId>で強制終了しました。  

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

初心者のGo言語 -37- <CommandContext>

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

環境

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

CommandContext関数

func CommandContext(ctx context.Context, name string, arg ...string) *Cmd

関数の説明 CommandContext は Command に似ていますが、コンテキストが含まれています。

コマンドが単独で完了する前にコンテキストが完了した場合、提供されたコンテキストは (cmd.Cancel または os.Process.Kill を呼び出して) プロセスを中断するために使用されます。

CommandContext は、コマンドの Cancel 関数を設定して、その Process で Kill メソッドを呼び出し、その WaitDelay を未設定のままにします。呼び出し元は、コマンドを開始する前にこれらのフィールドを変更することで、キャンセルの動作を変更できます。

[contextパッケージ] WithDeadline関数

func WithDeadline(parent Context, d time.Time) (Context, CancelFunc)

関数の説明 WithDeadline は、期限が d までに調整された親コンテキストのコピーを返します。親の期限がすでに d より早い場合、WithDeadline(parent, d) は意味的に親と同等です。返されたコンテキストの Done チャネルは、期限が切れたとき、返されたキャンセル関数が呼び出されたとき、または親コンテキストの Done チャネルが閉じられたときのいずれか早い方で閉じられます。

このコンテキストをキャンセルすると、関連付けられているリソースが解放されるため、コードは、このコンテキストで実行されている操作が完了したらすぐにキャンセルを呼び出す必要があります。

[contextパッケージ] WithTimeout関数

func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

関数の説明 WithTimeout は WithDeadline(parent, time.Now().Add(timeout)) を返します。

このコンテキストをキャンセルすると、それに関連付けられているリソースが解放されるため、コードは、このコンテキストで実行されている操作が完了したらすぐにキャンセルを呼び出す必要があります。

◆テストコード

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("Start")
    time.Sleep(3 * time.Second)
    fmt.Println("End")
}

コードの説明 exec.CommandContext()関数を試すためのテストコードです。
time.Sleep()関数を使って、3秒間一時停止します。
起動時に標準出力に"Start"と出力し、3秒後に"End"と出力します。
予め'go build sleep.go`で実行可能ファイルを作成しておきます。

◆テストコード

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "os/exec"
    "strconv"
    "time"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "Timeoutまでの秒数を指定してください。")
        os.Exit(1)
    }

    i, _ := strconv.Atoi(os.Args[1])
    t := time.Duration(i)

    ctx, cancel := context.WithTimeout(context.Background(), t*time.Second)
    defer cancel()

    c := exec.CommandContext(ctx, "./sleep.exe")
    o, e := c.StdoutPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", e))
        os.Exit(1)
    }

    if err := c.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", err))
        os.Exit(1)
    }

    b, _ := io.ReadAll(o)
    fmt.Printf("%s\n", b)

    if err := c.Wait(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", err))
        os.Exit(1)
    }
}

コードの説明 起動時の引数をcontext.WithTimeout()関数のタイムアウト値に指定してコンテキストを生成しています。このコンテキストをexec.CommandContext()関数の引数に渡していますので、 ./sleep.exeモジュールの実行時間が、起動時の引数を超えるとプロセスが中断されます。

◆実行結果(Windows)

実行結果の説明 タイムアウト値に2秒を指定して実行しました。
Sleep.exeの処理時間が3秒かかるので、処理中にプロセスが中断したため"End"が出力されませんでした。
さらに、Cmd.Wait()関数のerr戻り値の値がexit status 1となっていました。

◆実行結果(Windows)

実行結果の説明 タイムアウト値に4秒を指定して実行しました。
Sleep.exeの処理時間を超えていたためプロセスが中断されないで"End"が出力されました。

◆実行結果(Linux)

実行結果の説明 Linuxで実行した場合は、中断時にsignal: killedと表示されました。
Windowsより分かりやすいエラー内容でした。

◆テストコード

package main

import (
    "context"
    "fmt"
    "io"
    "os"
    "os/exec"
    "strconv"
    "time"
)

func main() {
    const layout = "2006-01-02 15:04:05"

    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "期限までの秒数を指定してください。")
        os.Exit(1)
    }
    i, _ := strconv.Atoi(os.Args[1])
    t := time.Duration(i)

    n := time.Now()
    d := n.Add(t * time.Second)
    fmt.Printf("開始時刻:[%s]\n", n.Format(layout))
    fmt.Printf("期限時刻:[%s]\n", d.Format(layout))

    ctx, cancel := context.WithDeadline(context.Background(), d)
    defer cancel()

    c := exec.CommandContext(ctx, "./sleep.exe")
    o, e := c.StdoutPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", e))
        os.Exit(1)
    }

    if err := c.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", err))
        os.Exit(1)
    }

    b, _ := io.ReadAll(o)
    fmt.Printf("%s\n", b)

    if err := c.Wait(); err != nil {
        fmt.Printf("中断時刻:[%s]\n", time.Now().Format(layout))
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", err))
        os.Exit(1)
    }
    fmt.Printf("終了時刻:[%s]\n", time.Now().Format(layout))
}

コードの説明 期限(Time)が来たら処理を中断するcontext.WithDeadline()関数を使用してコンテキストを生成しています。 今回は、起動時に指定した秒数が経過したら処理を中断するようにしています。 実行すると、開始時刻と期限時刻(開始時刻+起動時の引数秒)を標準出力に出力します。
処理が中断または完了すると、その時の時刻を標準出力に出力します。

◆実行結果(Windows)

実行結果の説明 期限を2秒後にして実行しました。
期限時刻が開始時刻の2秒後になっており、2秒後に処理が中断しexit status 1が出力されました。

◆実行結果(Windows)

実行結果の説明 期限を4秒後にして実行しました。
期限時刻が開始時刻の4秒後になっていますが、sleep.exeの処理が終わった3秒後に完了しています。

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

初心者のGo言語 -36- <Error,Unwrap>

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

環境

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

Error型

type Error struct {
    // Name is the file name for which the error occurred.
    Name string
    // Err is the underlying error.
    Err error
}

type Errorの説明 ファイルを実行可能ファイルとして分類できない場合、LookPath によってエラーが返されます。

◆テストコード

package main

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

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage:ErrorTest fileName")
        os.Exit(1)
    }

    p, e := exec.LookPath(os.Args[1])
    if e != nil {
        fmt.Fprintf(os.Stderr, "型情報=[%T]\n", e)
        ee := e.(*exec.Error)
        fmt.Fprintf(os.Stderr, "name=[%s]\n", ee.Name)
        fmt.Fprintf(os.Stderr, "err=[%v]\n", ee.Err)
    } else {
        fmt.Printf("Path=[%s]\n", p)
    }
}

コードの説明 exec.LookPath関数で、起動時の引数で指定された名前の実行可能ファイルを検索します。 エラーが戻ってきた場合は、*exec.Errorに型変換して、変数NameとErrの値を標準エラーに出力します。

◆実行結果(Windows)

実行結果の説明 ErrorTest.goという実行形式ではないファイルを指定しましたが、エラーにならないでパスが表示されました。
ファイルが存在しない場合は、file does not exist(ファイルが存在しません)のエラーが発生しました。

◆実行結果(Linux)

実行結果の説明 Linuxの場合は、実行できないファイルを指定した場合、permission denied(アクセス拒否)のエラーが発生しました。

Error関数

func (e *Error) Error() string

関数の説明無し

◆テストコード

package main

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

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage:ErrorTest fileName")
        os.Exit(1)
    }

    p, e := exec.LookPath(os.Args[1])
    if e != nil {
        ee := e.(*exec.Error)
        fmt.Fprintf(os.Stderr, "Err()=[%s]\n", ee.Error())
    } else {
        fmt.Printf("Path=[%s]\n", p)
    }
}

コードの説明 LookPath関数の戻り値errorをi.(T)を使って*exec.Errorにキャストしてから、
Error()関数の戻り値を出力しています。

◆実行結果(Linux)

実行結果の説明 エラー内容が出力されました。

Unwrap関数

func (e *Error) Unwrap() error

関数の説明無し

◆テストコード

package main

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

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintln(os.Stderr, "usage:ErrorTest fileName")
        os.Exit(1)
    }

    p, e := exec.LookPath(os.Args[1])
    if e != nil {
        ee := e.(*exec.Error)
        fmt.Fprintf(os.Stderr, "Unwrap=[%s]\n", ee.Unwrap().Error())
    } else {
        fmt.Printf("Path=[%s]\n", p)
    }
}

コードの説明 LookPath関数の戻り値errorをi.(T)を使って*exec.Errorにキャストしてから、
Unwrap().Error()関数の戻り値を出力しています。

◆実行結果(Linux)

実行結果の説明 エラー内容が出力されました。

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

初心者のGo言語 -35- <StderrPipe,StdinPipe,StdoutPipe,String,Wait>

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

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20.3 windows/amd64

StderrPipe関数

func (c *Cmd) StderrPipe() (io.ReadCloser, error)

関数の説明 StderrPipe は、コマンドの開始時にコマンドの標準エラーに接続されるパイプを返します。
Wait は、コマンドの終了を確認した後にパイプを閉じるため、ほとんどの呼び出し元はパイプ自体を閉じる必要はありません。したがって、パイプからのすべての読み取りが完了する前に Wait を呼び出すのは正しくありません。同じ理由で、StderrPipe を使用するときに Run を使用するのは正しくありません。慣用的な使用法については、StdoutPipe の例を参照してください。

◆テストコード

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Fprint(os.Stdout, "stdout!")
    fmt.Fprint(os.Stderr, "stderr!")
}

コードの説明 標準出力に"stdout!"と出力。標準エラーに"stderr!"を出力するプログラムです。
テストで使うので、予めgo build PrintOut.goを実行し、PrintOut.exeを作成しておきます。

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./printOut.exe")
    s, e := c.StderrPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StderrPipe", e))
    }

    if err := c.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", err ))
    }

    readPrint(s)

    if err := c.Wait(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", err ))
    }
}

コードの説明 先に作成したPrintOut.exeコマンドをCmd.Start()関数を使って実行します。
コマンド実行時にCmd.StderrPipe()関数で標準エラーのパイプを生成し、
readPrint()関数でパイプから読み取った内容を標準出力に出力しています。

◆実行結果(Windows)

実行結果の説明 パイプを通して標準エラーに出力された文字列を受け取り標準出力に出力できました。
パイプを作成していない標準出力の内容は出力されませんでした。

では、説明に正しくないと記載されていたCmd.Wait()関数の後にパイプを呼び出したらどうなるでしょうか。

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./printOut.exe")
    s, e := c.StderrPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StderrPipe", e))
    }

    if err := c.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", err))
    }

    if err := c.Wait(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", err))
    }

    readPrint(s)
}

コードの説明 パイプの内容を読み込んで標準出力に出力するreadPrint()関数をCmd.Wait()関数の後に実行するように変更しました。

◆実行結果(Windows)

実行結果の説明 file already closed(ファイルはすでに閉じられています)というエラーが発生し、標準エラーの内容が出力されませんでした。

では、もう一つ説明に正しくないと記載されていたCmd.Run()関数を使うとどうなるでしょうか。

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./printOut.exe")
    s, e := c.StderrPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StderrPipe", e))
    }

    if err := c.Run(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StderrPipe", err))
    }

    readPrint(s)
}

コードの説明 Cmd.Start()関数をCmd.Run()関数に変更し、Cmd.Wait()関数は不要なので呼ばないように変更しました。

◆実行結果(Windows)

実行結果の説明 同じく、file already closed(ファイルはすでに閉じられています)というエラーが発生し、標準エラーの内容が出力されませんでした。

StdinPipe関数

func (c *Cmd) StdinPipe() (io.WriteCloser, error)

関数の説明 StdinPipe は、コマンドの開始時にコマンドの標準入力に接続されるパイプを返します。
Wait がコマンドの終了を確認した後、パイプは自動的に閉じられます。呼び出し元は Close を呼び出すだけで、パイプを強制的に早く閉じることができます。たとえば、実行中のコマンドが標準入力が閉じられるまで終了しない場合、呼び出し元はパイプを閉じる必要があります。

◆テストコード

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var s string
    for {
        _, e := fmt.Fscan(os.Stdin, &s)
        if e == io.EOF || s == "End" {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
        }
        fmt.Printf("%s\n", s)
    }
}

コードの説明 標準入力で受け取った文字列を"<>"で囲って標準出力に出力するプログラムです。
"End"と入力すると処理を終了します。
テストで使うので、予めgo build ReadPrint.goを実行し、ReadPrint.exeを作成しておきます。

◆実行結果(Windows)

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF || a == "End" {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./ReadPrint.exe")

    so, e := c.StdoutPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", e))
    }

    si, e2 := c.StdinPipe()
    if e2 != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdinPipe", e2))
    }

    _, e = fmt.Fprintln(si, "test")
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fprintln", e))
    }

    e = c.Start()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", e))
    }

    _, e = fmt.Fprintln(si, "test2")
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fprintln", e))
    }

    si.Close()

    readPrint(so)

    e = c.Wait()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", e))
    }
}

コードの説明 ReadPrint.exeコマンドの標準出力のパイプを代入したso変数
ReadPrint.exeコマンドの標準入力のパイプを代入したsi変数
を作成し、
ReadPrint.exeコマンドにsi変数を使って"test"と"test2"という文字列を渡し、
so変数を使ってReadPrint.exeコマンドの標準出力を受け取って出力してます。

◆実行結果(Windows)

実行結果の説明 "test"が"<test>"になり、"test2"が"<test2>"になって出力されました。

StdoutPipe関数

func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

関数の説明 StdoutPipe は、コマンドの開始時にコマンドの標準出力に接続されるパイプを返します。
Wait は、コマンドの終了を確認した後にパイプを閉じるため、ほとんどの呼び出し元はパイプ自体を閉じる必要はありません。したがって、パイプからのすべての読み取りが完了する前に Wait を呼び出すのは正しくありません。同じ理由で、StdoutPipe を使用するときに Run を呼び出すのは正しくありません。慣用的な使用法については、例を参照してください。

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./printOut.exe")
    s, e := c.StdoutPipe()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", e))
    }

    if err := c.Start(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", err))
    }

    readPrint(s)

    if err := c.Wait(); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", err))
    }
}

コードの説明 先に作成したPrintOut.exeコマンドをCmd.Start()関数を使って実行します。 コマンド実行時にCmd.StdoutPipe()関数で標準出力のパイプを生成し、 readPrint()関数でパイプから読み取った内容を標準出力に出力しています。

◆実行結果(Windows)

実行結果の説明 パイプを通して標準出力に出力された文字列を受け取り標準出力に出力できました。 パイプを作成していない標準エラーの内容は出力されませんでした。

広告の下に続きます。

String関数

func (c *Cmd) String() string

関数の説明 Stringは、人間が読める形式の c の説明を返します。デバッグのみを目的としています。特に、シェルへの入力として使用するのには適していません。 String の出力は、Go のリリースによって異なる場合があります。

◆テストコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    c := exec.Command("./test.exe")
    fmt.Printf("[%s]\n", c.String())
}

コードの説明 Cmd.String()関数の戻り値を標準出力に出力します。

◆実行結果(Windows)

実行結果の説明 コマンド名が出力されました。

Wait関数

func (c *Cmd) Wait() error

関数の説明 Wait は、コマンドが終了するのを待ち、stdin へのコピー、または stdout または stderr からのコピーが完了するのを待ちます。
コマンドは Start によって開始されている必要があります。
コマンドが実行され、stdin、stdout、および stderr のコピーに問題がなく、ゼロの終了ステータスで終了する場合、返されるエラーは nil です。
コマンドの実行に失敗するか、正常に完了しない場合、エラーのタイプは ExitError です。 I/O の問題では、他の種類のエラーが返される場合があります。
c.Stdin、c.Stdout、または c.Stderr のいずれかが
os.File でない場合、Wait は、プロセスとの間でコピーを行うそれぞれの I/O ループが完了するまで待機します。
Wait は、Cmd に関連付けられたすべてのリソースを解放します。

◆テストコード

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    var s string
    for {
        _, e := fmt.Fscan(os.Stdin, &s)
        if e == io.EOF {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
        }
        fmt.Fprintf(os.Stdout, "stdout[%s]\n", s)
        fmt.Fprintf(os.Stderr, "stderr[%s]\n", s)
    }
}

コードの説明 標準入力で受け取った文字列を、 標準出力に"stdout[文字列]"と出力。標準エラーに"stderr[文字列]"を出力するプログラムです。 テストで使うので、予めgo build PrintOut2.goを実行し、PrintOut2.exeを作成しておきます。

◆テストコード

package main

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

func readPrint(r io.Reader) {
    var a string
    for {
        _, e := fmt.Fscan(r, &a)
        if e == io.EOF || a == "End" {
            break
        }
        if e != nil {
            fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fscan", e))
            break
        }
        fmt.Printf("%s\n", a)
    }
}

func main() {

    c := exec.Command("./ReadPrint2.exe")

    se, see := c.StderrPipe()
    if see != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", see))
    }

    so, soe := c.StdoutPipe()
    if soe != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdoutPipe", soe))
    }

    si, sie := c.StdinPipe()
    if sie != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.StdinPipe", sie))
    }

    _, e := fmt.Fprintln(si, "test")
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("fmt.Fprintln", e))
    }

    e = c.Start()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Start", e))
    }
    si.Close()

    readPrint(se)
    readPrint(so)

    e = c.Wait()
    if e != nil {
        fmt.Fprintf(os.Stderr, "%v\n", os.NewSyscallError("Cmd.Wait", e))
    }
}

コードの説明 ReadPrint2.exeコマンドの標準エラーのパイプを代入したse変数 ReadPrint2.exeコマンドの標準出力のパイプを代入したso変数 ReadPrint2.exeコマンドの標準入力のパイプを代入したsi変数 を作成し、 ReadPrint.exeコマンドにsi変数を使って"test"という文字列を渡し、 se変数とso変数を使ってReadPrint.exeコマンドの出力を受け取って標準出力に出力してます。

◆実行結果(Windows)

実行結果の説明 "test"が"stderr[test]"と"stdout[test]"になって出力されました。

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

初心者のGo言語 -34- <Start>

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

環境

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

Start関数

func (c *Cmd) Start() error

関数の説明Start は、指定されたコマンドを開始しますが、完了するまで待機しません。
Start が正常に返されると、c.Process フィールドが設定されます。
Start の呼び出しが成功した後、関連するシステム リソースを解放するために、Wait メソッドを呼び出す必要があります。

◆テストコード

package main

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

func main() {
    c := exec.Command("./sleep.exe", "5")
    n := time.Now()
    fmt.Printf("測定開始: %1.1f 秒経過\n", time.Since(n).Seconds())
    e := c.Start()
    fmt.Printf("Start関数後: %1.1f 秒経過\n", time.Since(n).Seconds())
    if e != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", e)
    }
    e = c.Wait()
    fmt.Printf("Wait関数後: %1.1f 秒経過\n", time.Since(n).Seconds())
    if e != nil {
        fmt.Fprintf(os.Stderr, "error: %v\n", e)
    }
}

コードの説明 前回作成したsleep.exeコマンドをCmd.Start()関数を使って実行します。
Cmd.Start()関数後とCmd.Wait()関数後に経過時間を表示しています。

◆実行結果(Windows)

実行結果の説明 Cmd.Run()関数と異なり、Cmd.Start()関数は、即時返ってきました。
Cmd.Wait()関数を呼ぶと、Sleep.exeコマンドの実行完了まで待つことがわかりました。

プログラムを実行したことで、
Cmd.Wait()関数は、実行したコマンドが終了するまで「待つ」ことがわかりました。
本体のプロセスと実行したコマンドが同時に動いていれば、 本体のプロセスの時間が経過すれば、Cmd.Wait()関数の待ち時間も減るか試してみます。

◆テストコード

package main

import (
    "fmt"
    "os"
    "os/exec"
    "strconv"
    "time"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Println("usage: StartTestK1.exe second")
    }
    i, _ := strconv.Atoi(os.Args[1])
    t := time.Duration(i)
    time.Sleep(t * time.Second)

    c := exec.Command("./sleep.exe", "5")
    e := c.Start()
    if e != nil {
        fmt.Printf("error: %v\n", e)
    }
    time.Sleep(t * time.Second)

    n := time.Now()
    fmt.Printf("測定開始: %1.1f 秒経過\n", time.Since(n).Seconds())
    e = c.Wait()
    fmt.Printf("Wait関数後: %1.1f 秒経過\n", time.Since(n).Seconds())
    if e != nil {
        fmt.Printf("error: %v\n", e)
    }
}

コードの説明 sleep.exeコマンド実行後に、任意の秒数プロセスを停止し、Cmd.Wait()関数の実行時間を測定します。
Cmd.Wait()関数の実行時間が「5秒-プロセス停止時間」になれば、同時に実行していたことになります。

◆実行結果(Windows)

実行結果の説明 Cmd.Wait()関数の実行時間が、5秒-"コマンド実行引数で指定した秒"になっていることが確認できました。
また、プロセスの待機時間がコマンドの実行時間よりも長い場合は、Cmd.Wait()関数が即時終わること後が確認できました。

関数の説明にc.Processフィールドが設定されると記載されています。
このc.Processフィールドの値を出力してみます。

c.Process フィールド

type Process struct {
    Pid int
    // contains filtered or unexported fields
}

説明 Process は、StartProcess によって作成されたプロセスに関する情報を格納します。

◆テストコード

package main

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

func printPid(p *os.Process) {
    if p == nil {
        fmt.Println("Process is nil.")
        return
    }
    fmt.Printf("pid=[%d]\n", p.Pid)
}

func main() {
    c := exec.Command("./sleep.exe", "1")
    printPid(c.Process)

    e := c.Start()
    printPid(c.Process)
    if e != nil {
        fmt.Printf("error: %v\n", e)
    }

    e = c.Wait()
    printPid(c.Process)
    if e != nil {
        fmt.Printf("error: %v\n", e)
    }
}

コードの説明 exec.Command()関数、Cmd.Start()関数およびCmd.Wait()関数を呼んだ後にc.Processフィールドを出力すします。もし、c.Processnilの場合は、"Process is nil."と出力します。

◆実行結果(Windows)

実行結果の説明 Cmd.Start()関数を呼んだ後に、c.Processフィールドが設定されてpidが出力されることが確認できました。

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