» Make grep CLI App in Go » 2. Development » 2.7 Support Pipes

Support Pipes

In Unix-like operating systems (such as Linux and macOS), a pipe is a mechanism for interprocess communication (IPC) that allows the output of one process to be used as the input of another.

Pipes are represented by the | symbol in shell commands. When you use a command like command1 | command2, it means that the standard output of command1 is connected to the standard input of command2.

For example:

cat file.txt | grep "pattern"

In this command, the content of file.txt is piped as input to the grep command, which searches for the specified pattern.

Modify main.go to make file_path argument optional:

@@ -12,7 +12,7 @@ import (
 func main() {
        // Set custom usage message
        flag.Usage = func() {
-               fmt.Fprintf(os.Stderr, "Usage: %s [options] pattern file_path\n", os.Args[0])
+               fmt.Fprintf(os.Stderr, "Usage: %s [options] pattern [file_path]\n", os.Args[0])
                fmt.Println("Options:")
                flag.PrintDefaults()
        }
@@ -30,12 +30,15 @@ func main() {
        // pattern - The pattern to search for
        // file_path - The path to the file to search in
        args := flag.Args()
-       if len(args) < 2 {
-               fmt.Println("Both pattern and file_path are required.")
+       if len(args) < 1 {
+               fmt.Println("Argument `pattern` is required.")
                flag.Usage()
                os.Exit(0)
        }
-       pattern, filePath := args[0], args[1]
+       pattern, filePath := args[0], ""
+       if len(args) > 1 {
+               filePath = args[1]
+       }
 
        options := &grep.Options{}
        if *ignoreCaseFlag {

You cannot do a recursive search when there's no file paths, modify main.go:

@@ -48,7 +51,7 @@ func main() {
        var result grep.MatchResult
        var err error
 
-       if *recursiveFlag {
+       if *recursiveFlag && filePath != "" {
                result, err = grep.GrepRecursive(pattern, filePath, options)
                if err != nil {
                        log.Fatal("Failed to do recursive grep, error:", err)

Read the content from standard input when file_path is empty in pkg/grep/search.go:

func readFileLines(filePath string) ([]string, error) {
	var scanner *bufio.Scanner
	if filePath == "" { // Read from standard input
		scanner = bufio.NewScanner(os.Stdin)
	} else { // Read from the file
		file, err := os.Open(filePath)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		// Create a scanner to read the file line by line
		scanner = bufio.NewScanner(file)
	}

	var lines []string
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return lines, nil
}

Now you can do this:

cat main.go | go run main.go -n result

Its result:

51: var result grep.MatchResult
55: result, err = grep.GrepRecursive(pattern, filePath, options)
60: result, err = grep.Grep(pattern, filePath, options)
67: fmt.Println(grep.GrepCount(result))
69: printResult(result, *lineNumberFlag)
73: func printResult(result grep.MatchResult, lineNumberOption bool) {
75: fileCount := len(result)
77: for filePath, items := range result {

After re-installation, your gorep supports pipes as well.

cat main.go | gorep -n result

It produces the same result as above.