Goの勉強で, 上記の記事の Go版を書いてみました
基本
forkでなくて go routineを使っています. 終了の待ち合わせはチャネルを
用いました.
package main import ( "bufio" "fmt" "io" "log" "os" "os/exec" ) func createSubprocess(command string) chan bool { ch := make(chan bool) go func() { cmd := exec.Command(command) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { log.Println(err) } else { err = cmd.Wait() if err != nil { log.Println(err) } } ch <- true close(ch) }() return ch } func waitSubprocess(ch <-chan bool) { <-ch } func main() { bio := bufio.NewReader(os.Stdin) for { fmt.Printf("-> ") line, hasMoreLine, err := bio.ReadLine() if !hasMoreLine && err == io.EOF { fmt.Println("Bye") break } if err != nil { log.Fatal(err) } ch := createSubprocess(string(line)) waitSubprocess(ch) } }
コマンドラインのパース
入力文字列を strings.Splitで区切って, さらに各要素を strings.TrimSpaceで
空白を除去しています.
package main import ( "bufio" "fmt" "io" "log" "os" "os/exec" "strings" ) func parseLine(line string) []string { words := strings.Split(strings.TrimSpace(line), " ") command := make([]string, 0) for _, word := range words { trimed := strings.TrimSpace(word) command = append(command, trimed) } return command } func createSubprocess(command []string) chan bool { ch := make(chan bool) go func() { cmd := exec.Command(command[0], command[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { log.Println(err) } else { err = cmd.Wait() if err != nil { log.Println(err) } } ch <- true close(ch) }() return ch } func waitSubprocess(ch <-chan bool) { <-ch } func main() { bio := bufio.NewReader(os.Stdin) for { fmt.Printf("-> ") line, hasMoreLine, err := bio.ReadLine() if !hasMoreLine && err == io.EOF { fmt.Println("Bye") break } if err != nil { log.Fatal(err) } command := parseLine(string(line)) ch := createSubprocess(command) waitSubprocess(ch) } }
ワイルドカード
オリジナルは '*'と文字列比較をしていましたが, 正規表現を使うようにして
みました.
package main import ( "bufio" "fmt" "io" "log" "os" "os/exec" "path/filepath" "regexp" "strings" ) var globRegexp = regexp.MustCompile(`\*`) func parseLine(line string) ([]string, *os.File) { var output *os.File words := strings.Split(strings.TrimSpace(line), " ") commands := make([]string, 0) for _, word := range words { trimed := strings.TrimSpace(word) if globRegexp.Match([]byte(trimed)) { expandeds, err := filepath.Glob(trimed) if err != nil { log.Fatal(err) } commands = append(commands, expandeds...) } else { commands = append(commands, trimed) } } return commands, output } func createSubprocess(command []string, output *os.File) chan bool { ch := make(chan bool) go func() { cmd := exec.Command(command[0], command[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { log.Println(err) } else { err = cmd.Wait() if err != nil { log.Println(err) } } ch <- true close(ch) }() return ch } func waitSubprocess(ch <-chan bool) { <-ch } func main() { bio := bufio.NewReader(os.Stdin) for { fmt.Printf("-> ") line, hasMoreLine, err := bio.ReadLine() if !hasMoreLine && err == io.EOF { fmt.Println("Bye") break } if err != nil { log.Fatal(err) } command, output := parseLine(string(line)) ch := createSubprocess(command, output) waitSubprocess(ch) } }
リダイレクト
Cmdの Stdoutを差し替えることで, リダイレクトを実現しました
package main import ( "bufio" "fmt" "io" "log" "os" "os/exec" "path/filepath" "regexp" "strings" ) var globRegexp = regexp.MustCompile(`\*`) var redirectRegex = regexp.MustCompile(`>(\S+)`) func parseLine(line string) ([]string, *os.File) { var output *os.File words := strings.Split(strings.TrimSpace(line), " ") command := make([]string, 0) for _, word := range words { trimed := strings.TrimSpace(word) if globRegexp.Match([]byte(trimed)) { expandeds, err := filepath.Glob(trimed) if err != nil { log.Fatal(err) } command = append(command, expandeds...) } else if redirectRegex.Match([]byte(trimed)) { matches := redirectRegex.FindAllStringSubmatch(trimed, -1) file := matches[0][1] var err error output, err = os.Create(file) if err != nil { log.Fatal(err) } } else { command = append(command, trimed) } } return command, output } func createSubprocess(command []string, output *os.File) chan bool { ch := make(chan bool) go func() { cmd := exec.Command(command[0], command[1:]...) if output != nil { cmd.Stdout = output } else { cmd.Stdout = os.Stdout } cmd.Stderr = os.Stderr err := cmd.Start() if err != nil { log.Println(err) } else { err = cmd.Wait() if err != nil { log.Println(err) } } ch <- true close(ch) }() return ch } func waitSubprocess(ch <-chan bool) { <-ch } func main() { bio := bufio.NewReader(os.Stdin) for { fmt.Printf("-> ") line, hasMoreLine, err := bio.ReadLine() if !hasMoreLine && err == io.EOF { fmt.Println("Bye") break } if err != nil { log.Fatal(err) } command, output := parseLine(string(line)) ch := createSubprocess(command, output) waitSubprocess(ch) } }
パイプ
パイプの使い方はオリジナルのものと同じです.
package main import ( "bufio" "fmt" "io" "log" "os" "os/exec" "path/filepath" "regexp" "strings" ) var globRegexp = regexp.MustCompile(`\*`) func parseLine(line string) [][]string { words := strings.Split(strings.TrimSpace(line), " ") commands := make([][]string, 0) cmd := make([]string, 0) for _, word := range words { trimed := strings.TrimSpace(word) if globRegexp.Match([]byte(trimed)) { expandeds, err := filepath.Glob(trimed) if err != nil { log.Fatal(err) } cmd = append(cmd, expandeds...) } else if trimed == "|" { commands = append(commands, cmd) cmd = make([]string, 0) } else { cmd = append(cmd, trimed) } } commands = append(commands, cmd) return commands } func createSubprocess(command []string, in *io.PipeReader, out *io.PipeWriter, ch chan<- bool) { go func() { cmd := exec.Command(command[0], command[1:]...) if in == nil { cmd.Stdin = os.Stdout } else { cmd.Stdin = in } if out == nil { cmd.Stdout = os.Stdout } else { cmd.Stdout = out } err := cmd.Start() if err != nil { log.Println(err) } else { err = cmd.Wait() if err != nil { log.Println(err) } if in != nil { in.Close() } if out != nil { out.Close() } } ch <- true }() } func waitSubprocess(processes int, ch <-chan bool) { for i := 0; i < processes; i++ { <-ch } } type DataPipes struct { in *io.PipeReader out *io.PipeWriter } func makeSubprocessPipes(processes int) []*DataPipes { pipes := make([]*DataPipes, 0) for i := 0; i < processes; i++ { in, out := io.Pipe() data := &DataPipes{in, out} pipes = append(pipes, data) } return pipes } func main() { bio := bufio.NewReader(os.Stdin) for { fmt.Printf("-> ") line, hasMoreLine, err := bio.ReadLine() if !hasMoreLine && err == io.EOF { fmt.Println("Bye") break } if err != nil { log.Fatal(err) } commands := parseLine(string(line)) processes := len(commands) pipes := makeSubprocessPipes(processes) ch := make(chan bool, len(commands)) for i, command := range commands { var in *io.PipeReader var out *io.PipeWriter if i != 0 { in = pipes[i-1].in } if i != len(commands)-1 { out = pipes[i].out } createSubprocess(command, in, out, ch) } waitSubprocess(processes, ch) } }