Creating Your First CLI App in Golang

Sagar Sonwane
6 min readApr 9, 2021

The purpose of this blog is to help those who are new to Go Programming Language and wants to create an application or have never worked on creating a CLI application before.
Though this will be a very small step towards learning Go applications, it will be a potentially useful one.

So let’s start.

Let's consider a scenario, where you have a log file. A standard logfile format has basically 3 fields Timestamp and Log Level followed by a message of some sort.

Just imagine, in a real-world application there are hundreds or thousands of logs of such kind and you are tasked to find a way to organize this log file and fetch logs that are specifically indicating errors that occurred in the application.

So to solve this problem, we will create a Go Application which will go through all the logs in the log file and will separate out logs with ERROR log level.

So let’s start with creating a Go Application.

Creating a Go CLI App

Create a Project Directory, say Golang-First-CLI-App.

$ mkdir Golang-First-CLI-App
$ cd Golang-First-CLI-App

While inside our new project directory, we’re now going to initialize our project as a Go module. This means anybody will be able to install our Go code off Github if they so choose. I know I’m going to save my repo to https://github.com/sagar23sj/Golang-First-CLI-App, so we run the following:

# Creating a Go Module$ go mod init github.com/sagar23sj/Golang-First-CLI-App
go: creating new go.mod: module github.com/sagar23sj/Golang-First-CLI-App

A new file will now appear in your project directory called go.mod.
go.mod file contains information about our module for others. As we install dependencies for our project, these dependencies, and their respective versions will be stored here.

Create Source File : main.go

All source files start with a package declaration, so we will go ahead and declare package main. Now in order for go to figure out the entry point for our application, we need to have a well-known entry point. So we will create the main function here.

# main.gopackage mainfunc main() {}

Now from other programming languages, you might have been used to seeing command line parameter as first parameter to main function which accepts command line parameters passed as the application starts up.

Now, Go also has access to command line parameters, but it is not necessary to deal with them every time.

Now, we will start constructing our application.

package mainimport (
"bufio"
"fmt"
"os"
"strings"
)
func main() { //here we are using an Open() function from os package in golang,
//which will return the File Pointer through which we can
//perform I/O Operations on File
f, err := os.Open("sample.log")
if err != nil {
fmt.Println("error opening log file : %+v", err)
return
}
//defer functions will be executed at the end main function,
//ensuring Opened File is Closed before exiting making
//it unusable for I/O
defer f.Close()
//Now using bufio package in golang to read the content of file
// I am going to call a reader. This is just designed to read some
//type of input stream. In this case our input stream is a
// file but readers can be anything. It can be stream from a
//network connection or a string in our program or a file
r := bufio.NewReader(f)
//Running an infinite for loop and will read every line in file
for{ //creating a reader that allows reading the content of the file
s, err := r.ReadString('\n')
if err != nil {
break
}
//now here we will setup filtering log for log-level [ERROR]
if strings.Contains(s, "[ERROR]") {
fmt.Println(s)
}
}
}

Output :

$ go run main.go 2012-02-03 18:35:34 SampleClass0 [ERROR] incorrect id  18864385132012-02-03 18:55:54 SampleClass1 [ERROR] incorrect id  11592815322012-02-03 19:25:27 SampleClass4 [ERROR] incorrect id  922985906java.lang.Exception: 2012-02-03 19:40:18 SampleClass1 [ERROR] incorrect format for id 671681594java.lang.Exception: 2012-02-03 19:58:39 SampleClass4 [ERROR] incorrect format for id 1919705250java.lang.Exception: 2012-02-03 20:11:35 SampleClass1 [ERROR] incorrect format for id 1088486137

Now, is this what we wanted?? Partially, yes. This is just half the part, which is filtering.

Now, In the current implementation, we have a fixed location of log file and fixed log-level, but moving forward what we want is user must be able to specify the location of the log file and also the log-level we are reading.

Golang generally doesn't force us to use command line parameters, but if we do want to use it. We can use golang package called flag.

flag package implements command-line flag parsing.

Let’s now look at the code, where to add what to accept arguments from the command line.

package mainimport (
"bufio"
"fmt"
"os"
"flag"
"strings"
)
func main() { //earlier we had fixed log path, now we want to accept value
//from command line since go is strongly typed language, we need
//to mention what type of value we are expecting, here String
//there are 3 arguments to the method, 1. variable-name
//2.default value 3.help description to be showed on command line
path := flag.String("path", "sample.log", "The path to the log file that should be analysed") level := flag.String("level", "ERROR", "Log-Level to search, for eg. ERROR. Option : [ ERROR, INFO, FATAL, DEBUG, WARN, TRACE ]") //after declaring flags are not enough, we need to fetch the
//values from command line for that we use Parse() method
//in flag Package. This will populate the values in
//varaibles declared above.
flag.Parse() //now we will pass the pointer to path to Open method
//since flag.String() returns pointer to string
f, err := os.Open(*path)
if err != nil {
fmt.Println("error opening log file : %+v", err)
return
}
defer f.Close() r := bufio.NewReader(f) for{
s, err := r.ReadString('\n')
if err != nil {
break
}
//now here we will setup filtering log for log-level fetched
//from command line and stored into level variable
if strings.Contains(s, *level) {
fmt.Println(s)
}
}
}

Now, we can check 3 outputs in this case

Output 1: go run main.go

$ go run main.go2012-02-03 18:35:34 SampleClass0 [ERROR] incorrect id  18864385132012-02-03 18:55:54 SampleClass1 [ERROR] incorrect id  11592815322012-02-03 19:25:27 SampleClass4 [ERROR] incorrect id  922985906java.lang.Exception: 2012-02-03 19:40:18 SampleClass1 [ERROR] incorrect format for id 671681594java.lang.Exception: 2012-02-03 19:58:39 SampleClass4 [ERROR] incorrect format for id 1919705250java.lang.Exception: 2012-02-03 20:11:35 SampleClass1 [ERROR] incorrect format for id 1088486137

This is similar to earlier since we have set default values for our program.

Output 2: go run main.go -help

$ go run main.go -helpUsage of /tmp/go-build844696058/b001/exe/main:
-level string
Log-Level to search, for eg. ERROR. Option : [ ERROR, INFO, FATAL, DEBUG, WARN, TRACE ] (default "ERROR")
-path string
The path to the log file that should be analysed (default "sample.log")

When we pass argument -help, it will display the help document on command line that we mentioned in 3rd argument to the flag variables.

Output 3: go run main.go -level FATAL

$ go run main.go -level FATAL
2012-02-03 18:35:34 SampleClass4 [FATAL] system problem at id 1991281254
java.lang.Exception: 2012-02-03 20:11:35 SampleClass2 [FATAL] unrecoverable system problem at id 609774657

For this output to be fetched, we passed the command-line argument to our program as -level FATAL.

Seems like what we wanted from the start i.e to able to create a Go Application which accepts command-line arguments and does the operations based on that.

So that’s as simple as it is.

Now obviously, there is more to command-line applications with GO. The entire standard library is available so you can experiment.

I hope this gives you a taste of what it’s like to create a simple command-line application in Go.

Thank You.

Github-Link to the code : https://github.com/sagar23sj/Golang-First-CLI-App

--

--