go-pipe - 解答

実装コード

package main

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

func main() {
    if len(os.Args) < 5 {
        fmt.Fprintln(os.Stderr, "Usage: go-pipe file1 cmd1 cmd2 file2")
        os.Exit(1)
    }

    infile := os.Args[1]
    cmd1 := os.Args[2]
    cmd2 := os.Args[3]
    outfile := os.Args[4]

    if err := runPipeline(infile, cmd1, cmd2, outfile); err != nil {
        fmt.Fprintln(os.Stderr, "Error:", err)
        os.Exit(1)
    }
}

func runPipeline(infile, cmd1str, cmd2str, outfile string) error {
    // 入力ファイルを開く
    input, err := os.Open(infile)
    if err != nil {
        return fmt.Errorf("cannot open input: %w", err)
    }
    defer input.Close()

    // 出力ファイルを作成
    output, err := os.Create(outfile)
    if err != nil {
        return fmt.Errorf("cannot create output: %w", err)
    }
    defer output.Close()

    // コマンドをパース
    cmd1 := parseCommand(cmd1str)
    cmd2 := parseCommand(cmd2str)

    // パイプを作成
    pr, pw, err := os.Pipe()
    if err != nil {
        return fmt.Errorf("cannot create pipe: %w", err)
    }

    // cmd1: input -> pipe
    cmd1.Stdin = input
    cmd1.Stdout = pw
    cmd1.Stderr = os.Stderr

    // cmd2: pipe -> output
    cmd2.Stdin = pr
    cmd2.Stdout = output
    cmd2.Stderr = os.Stderr

    // 両方のコマンドを開始
    if err := cmd1.Start(); err != nil {
        return fmt.Errorf("cmd1 start: %w", err)
    }
    if err := cmd2.Start(); err != nil {
        return fmt.Errorf("cmd2 start: %w", err)
    }

    // パイプの書き込み側を閉じる(cmd1が終了したら)
    go func() {
        cmd1.Wait()
        pw.Close()
    }()

    // cmd2 の完了を待つ
    if err := cmd2.Wait(); err != nil {
        return fmt.Errorf("cmd2 wait: %w", err)
    }

    return nil
}

func parseCommand(cmdStr string) *exec.Cmd {
    parts := strings.Fields(cmdStr)
    if len(parts) == 0 {
        return exec.Command("true")
    }
    return exec.Command(parts[0], parts[1:]...)
}

低レベル実装(syscall版)

package main

import (
    "os"
    "syscall"
)

func runWithSyscall(infile, cmd1, cmd2, outfile string) error {
    // パイプを作成
    var pipeFd [2]int
    syscall.Pipe(pipeFd[:])

    // 入力ファイルを開く
    inFd, _ := syscall.Open(infile, syscall.O_RDONLY, 0)

    // 出力ファイルを作成
    outFd, _ := syscall.Open(outfile,
        syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0644)

    // 子プロセス1を作成
    pid1, _, _ := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
    if pid1 == 0 {
        // 子プロセス1
        syscall.Dup2(inFd, 0)       // stdin <- infile
        syscall.Dup2(pipeFd[1], 1)  // stdout -> pipe
        syscall.Close(pipeFd[0])
        syscall.Exec("/bin/sh", []string{"sh", "-c", cmd1}, os.Environ())
    }

    // 子プロセス2を作成
    pid2, _, _ := syscall.Syscall(syscall.SYS_FORK, 0, 0, 0)
    if pid2 == 0 {
        // 子プロセス2
        syscall.Dup2(pipeFd[0], 0)  // stdin <- pipe
        syscall.Dup2(outFd, 1)      // stdout -> outfile
        syscall.Close(pipeFd[1])
        syscall.Exec("/bin/sh", []string{"sh", "-c", cmd2}, os.Environ())
    }

    // 親プロセス: パイプを閉じて待機
    syscall.Close(pipeFd[0])
    syscall.Close(pipeFd[1])
    syscall.Close(inFd)
    syscall.Close(outFd)

    var status syscall.WaitStatus
    syscall.Wait4(int(pid1), &status, 0, nil)
    syscall.Wait4(int(pid2), &status, 0, nil)

    return nil
}