Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Mastering Go

You're reading from   Mastering Go Leverage Go's expertise for advanced utilities, empowering you to develop professional software

Arrow left icon
Product type Paperback
Published in Mar 2024
Publisher Packt
ISBN-13 9781805127147
Length 736 pages
Edition 4th Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Mihalis Tsoukalos Mihalis Tsoukalos
Author Profile Icon Mihalis Tsoukalos
Mihalis Tsoukalos
Arrow right icon
View More author details
Toc

Table of Contents (19) Chapters Close

Preface 1. A Quick Introduction to Go 2. Basic Go Data Types FREE CHAPTER 3. Composite Data Types 4. Go Generics 5. Reflection and Interfaces 6. Go Packages and Functions 7. Telling a UNIX System What to Do 8. Go Concurrency 9. Building Web Services 10. Working with TCP/IP and WebSocket 11. Working with REST APIs 12. Code Testing and Profiling 13. Fuzz Testing and Observability 14. Efficiency and Performance 15. Changes in Recent Go Versions 16. Other Books You May Enjoy
17. Index
Appendix: The Go Garbage Collector

Developing the which(1) utility in Go

Go can work with your operating system through a set of packages. A good way of learning a new programming language is by trying to implement simple versions of traditional UNIX utilities—in general, the only efficient way to learn a programming language is by writing lots of code in that language. In this section, you will see a Go version of the which(1) utility, which will help you understand the way Go interacts with the underlying OS and reads environment variables.

The presented code, which will implement the functionality of which(1), can be divided into three logical parts. The first part is about reading the input argument, which is the name of the executable file that the utility will be searching for. The second part is about reading the value stored in the PATH environment variable, splitting it, and iterating over the directories of the PATH variable. The third part is about looking for the desired binary file in these directories and determining whether it can be found or not, whether it is a regular file, and whether it is an executable file. If the desired executable file is found, the program terminates with the help of the return statement. Otherwise, it will terminate after the for loop ends and the main() function exits.

The presented source file is called which.go and is located under the ch01 directory of the GitHub repository of the book. Now, let us see the code, beginning with the logical preamble that usually includes the package name, the import statements, and other definitions with a global scope:

package main
import (
    "fmt"
    "os"
    "path/filepath"
)

The fmt package is used for printing onscreen, the os package is for interacting with the underlying operating system, and the path/filepath package is used for working with the contents of the PATH variable that is read as a long string, depending on the number of directories it contains.

The second logical part of the utility is the following:

func main() {
    arguments := os.Args
    if len(arguments) == 1 {
        fmt.Println("Please provide an argument!")
        return
    }
    file := arguments[1]
    path := os.Getenv("PATH")
    pathSplit := filepath.SplitList(path)
    for _, directory := range pathSplit {

First, we read the command line arguments of the program (os.Args) and save the first command line argument into the file variable. Then, we get the contents of the PATH environment variable and split it using filepath.SplitList(), which offers a portable way of separating a list of paths. Lastly, we iterate over all the directories of the PATH variable using a for loop with range, as filepath.SplitList() returns a slice.

The rest of the utility contains the following code:

        fullPath := filepath.Join(directory, file)
        // Does it exist?
        fileInfo, err := os.Stat(fullPath)
        if err != nil {
            continue
        }
        mode := fileInfo.Mode()
        // Is it a regular file?
        if !mode.IsRegular() {
            continue
        }
        // Is it executable?
        if mode&0111 != 0 {
            fmt.Println(fullPath)
            return
        }
    }
}

We construct the full path that we examine using filepath.Join(), which is used for concatenating the different parts of a path using an OS-specific separator—this makes filepath.Join() work on all supported operating systems. In this part, we also get some lower-level information about the file—keep in mind that UNIX considers everything as a file, which means that we want to make sure that we are dealing with a regular file that is also executable.

Executing which.go generates the following kind of output:

$ go run which.go which
/usr/bin/which
$ go run which.go doesNotExist

The last command could not find the doesNotExist executable—according to the UNIX philosophy and the way UNIX pipes work, utilities generate no output onscreen if they have nothing to say.

Although it is useful to print error messages onscreen, there are times that you need to keep all error messages together and be able to search for them later when it is convenient for you. In this case, you need to use one or more log files.

The next section discusses logging in Go.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime