Being a good citizen in the command line means using pipes.

Using the vertical bar | we can pass the output of a command as an input of another command, and chain multiple commands to provide a unique output.

I have made 2 tutorials that use pipes as their input, building gocowsay and gololcat, but I didn’t describe in detail the process to get input from a pipe, so here’s an article on this topic.

Here’s an example:

package main

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

func main() {
	info, err := os.Stdin.Stat()
    if err != nil {
        panic(err)
    }

	if info.Mode()&os.ModeCharDevice != 0 || info.Size() <= 0 {
		fmt.Println("The command is intended to work with pipes.")
		fmt.Println("Usage: fortune | gocowsay")
		return
	}

	reader := bufio.NewReader(os.Stdin)
	var output []rune

	for {
		input, _, err := reader.ReadRune()
		if err != nil && err == io.EOF {
			break
		}
		output = append(output, input)
	}

	for j := 0; j < len(output); j++ {
		fmt.Printf("%c", output[j])
	}
}

Let’s examine how this works. First interesting line:

info, err := os.Stdin.Stat()

os.Stdin, like Stdout and Stderr, is an open File. It points to the standard input file descriptor.

Stat() is a method of File that returns a FileInfo describing the file, providing information including mode and file size.

The next interesting piece is

info.Mode()&os.ModeCharDevice != 0

FileInfo.Mode() returns the uint32 mask that determines the file mode and permissions as a const (https://golang.org/pkg/os/#FileMode).

Usign a bitwise AND determines if the file mode is os.ModeCharDevice. This is a way to make sure that the file points to a ModeCharDevice, the Unix character device (terminal). Since the file points to os.Stdin, We’re basically excluding the input being a pipe.

Bitwise returns 0 if the mode is not the constant we pass. We could also check for

info.Mode()&os.ModeCharDevice == os.ModeCharDevice

that’s the same, but more readable.

If this is false, we have an additional check for

info.Size() <= 0

which combined with the previous check, makes sure have an input pipe, and that actually contains some bytes.

We could also check for

if info.Mode()&os.ModeNamedPipe != 0 {
	// we have a pipe input
}

and this will make sure the input comes from the pipe.

Next the program creates a reader

reader := bufio.NewReader(os.Stdin)

bufio is a package that implements buffered I/O, basically wrapping io.Reader and io.Writer.

We ask it to give us a buffered reader from os.Stdin, using the default buffer size which is 4096 bytes.

bufio.Reader provides many methods to read data: ReadByte, ReadBytes, ReadLine, ReadRune, ReadSlice, ReadString.

The example uses ReadRune so it can add each rune read into the var output []rune slice, and handle unicode chars without any effort.

Read more


Get my posts in your inbox every week!