kurumi-bioの雑記帳

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

初心者の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]"になって出力されました。

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