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
}