Wrote a simple program that calls "ls", then passes each line through regexp filtering for files that end in an "s". ls is used only for the purposes of learning the exec package. How can I improve the code below to be more correct/succinct/go-ish?
package main
import (
"bufio"
"fmt"
"os/exec"
"regexp"
)
func main() {
cmd := exec.Command("ls")
stdout, _ := cmd.StdoutPipe()
s := bufio.NewReader(stdout)
cmd.Start()
go cmd.Wait()
for {
l, _, err := s.ReadLine()
if err != nil {
break
}
if m, err := regexp.Match(".*s$", l); m && err == nil {
fmt.Println(string(l))
}
}
}
The Cmd.Output example in the standard documentation is pretty succinct. It doesn't do any text processing, but it shows how to execute a command and get the output with a single function call.
Here's a way to combine that example with yours,
package main
import (
"bytes"
"fmt"
"log"
"os/exec"
"regexp"
)
func main() {
out, err := exec.Command("ls").Output()
if err != nil {
log.Fatal(err)
}
fmt.Print(string(bytes.Join(regexp.MustCompile(".*s
").FindAll(out, -1), nil)))
}
If the goal is to get a broad overview of the package, experiment with a number of the functions and methods to learn their different capabilities. Experiment with command arguments. Experiment with the different fields of the Cmd struct. Try not to get too distracted with other packages like regexp, just look for the simplest examples that exercise a package feature.
Of course if you see how you might use an exec feature in a real application, try it. You'll learn that one feature in more depth.
For example,
package main
import (
"bufio"
"errors"
"fmt"
"io"
"os/exec"
)
func main() {
cmd := exec.Command("ls")
stdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Println(err)
return
}
ls := bufio.NewReader(stdout)
err = cmd.Start()
if err != nil {
fmt.Println(err)
return
}
for {
line, isPrefix, err := ls.ReadLine()
if isPrefix {
fmt.Println(errors.New("isPrefix: true"))
return
}
if err != nil {
if err != io.EOF {
fmt.Println(err)
return
}
break
}
fmt.Println(string(line))
}
err = cmd.Wait()
if err != nil {
fmt.Println(err)
return
}
}
Output (depends on your directory contents):
bench.go
temp.go
Add error checking and stop using regexp for this kind of example.
For any input data larger than the buffer size, ReadLine will inevitably return with isPrefix set to true at some point. ReadString(' ') does what you want - reading until end of line regardless of whether it fits in the buffer, extending the output string as is goes. That also means you could end up with an arbitrarily long string. On the plus side for ReadLine(), it does drop the " " or " " from the end of the line for you, so when using ReadString(), we have to do that ourselves.
package main
import (
"bufio"
"fmt"
"os/exec"
"io"
)
func main() {
var err error
var out io.Reader
// Create a command object
cmd := exec.Command("ls")
// Obtain a pipe to receive the stdout of the command
out, err = cmd.StdoutPipe(); if err != nil {
panic(err)
}
// Start the child process
err = cmd.Start(); if err != nil {
panic(err)
}
// bufio.Reader allows us to read all bytes until newline
s := bufio.NewReader(out)
for {
var line string
line, err = s.ReadString('
')
if err == io.EOF && len(line) == 0 {
// Good end of file with no partial line
break
}
if err == io.EOF {
err := fmt.Errorf("Last line not terminated: %q", line)
panic(err)
}
if err != nil {
panic(err)
}
line = line[:len(line)-1] // drop the '
'
if line[len(line)-1] == '' {
line = line[:len(line)-1] // drop the ''
}
if line[len(line)-1] == 's' { // Finally check for lines ending in 's'
fmt.Println(line)
}
}
// Wait for the result of the command; also closes our end of the pipe
err = cmd.Wait()
if err != nil {
panic(err)
}
}