Anonymous pipes

Different commands can be chained in a sequence when using a shell, making one command output the following command input. For example, consider the following command:

cat book_list.txt | grep "Game" | wc -l

Here, we are displaying a file, using the preceding command to filter rows containing a certain string and, finally, using the filtered output to count the lines.

This can be done programmatically in Go when the processes are created inside the application.

The io.Pipe function returns a reader/writer couple that is connected; whatever gets written to the pipe writer will be read by the pipe reader. The write operation is a blocking one, which means that all of the written data has to be read before a new write operation can be executed.

We already saw that exec.Cmd allows generic streams for its output and input—this allows us to use the values returned by the io.Pipe function to connect one process to the other.

First, we define the three commands, as follows:

  • cat with index 0
  • grep with index 1 
  • wc with index 2

Then, we can define the two pipes we need, as shown in the following code:

r1, w1 := io.Pipe()
r2, w2 := io.Pipe()

var cmds = []*exec.Cmd{
exec.Command("cat", "book_list.txt"),
exec.Command("grep", "Game"),
exec.Command("wc", "-l"),
}

Next, we link the input and output streams. We connect the cat (command 0) output and the grep (command 1) input, and we do the same for the grep output and the wc input:

cmds[1].Stdin, cmds[0].Stdout = r1, w1
cmds[2].Stdin, cmds[1].Stdout = r2, w2
cmds[2].Stdout = os.Stdout

We then start our commands, as follows:

for i := range cmds {
if err := cmds[i].Start(); err != nil {
log.Fatalln("Start", i, err)
}
}

We wait until the end of the execution of each command and we close the respective pipe writer; otherwise, the next command reader will hang. To make things easier, each pipe writer is an element in a slice, and each writer has the same index as the command that it is linked to. The last one is nil because the last command is not linked by a pipe:

for i, closer := range []io.Closer{w1, w2, nil} {
if err := cmds[i].Wait(); err != nil {
log.Fatalln("Wait", i, err)
}
if closer == nil {
continue
}
if err := closer.Close(); err != nil {
log.Fatalln("Close", i, err)
}
}

There are also other tools provided by the io package that can help simplify some operations.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.176.194