Although most of the software we use today is visual and easy to use, that doesn’t mean that CLI (command line) applications are useless, especially for developers who deal with CLI applications on a regular basis. Golang is perfect for building CLI applications, and we will introduce how to build a CLI application in Golang in the following.
For developers, there are a lot of CLI tools like npm, node, go, python, docker, kubectl, etc. which are very small, dependency-free, ideal for system management or automation tasks, etc.
We chose to use the famous Cobra library in Golang for our CLI tool development. Cobra is a powerful and modern CLI application library, and there are many well-known Go projects that use Cobra for building, such as Kubernetes, Docker, Hugo, etc.
Cobra is built on top of commands, parameters and identifiers: the
Commandsmeans execute actions
Argsare the execution parameters
Flagsare the identifiers for these actions
The basic execution commands are shown below.
For example, some of the command-line tools we normally use are
- git clone URL -bare
- go get -u URL
- npm install package -save
- kubectl get pods -n kube-system -l app=cobra
Let’s take a look at using Cobra, here we are using go1.13.3 and using Go Modules for package management.
Create a new directory called
my-calc as the project directory, and initialize modules.
After initialization, you can see that there is a
go.mod file under the project root. Now that we don’t have the
cobra library installed, run the following command to install it.
After successful installation, we can now initialize the scaffolding of the CLI application using the
cobra init command.
Note that newer versions of the cobra library require a
-pkg-name argument for initialization, which specifies the name of the module we initialized above. The init command above will create a minimal CLI application project.
main.go is the entry point for the CLI application, and the
Execute function under
cmd/root.go is called in
Then we’ll look at the
The root command is the most basic command of the CLI tools, for example, for the
go get URL we used earlier, where
go is the root command, and
get is a subcommand of the
go root command, and the cobra command is used directly in
root.go to initialize the
rootCmd structure, all other commands in the CLI will be subcommands of the
rootCmd root command. All other commands in the CLI will be subcommands of the root command
Here we remove the comment inside the
rootCmd variable in
cmd/root.go and add the phrase
fmt.Println("Hello Cobra CLI") to the
At this point we execute the following command under the root of the project to build.
This command generates a binary file named
my-calc in the root of the project, which can be executed directly to see the following output.
We know that the
init function is the first function called when initializing a package in Golang. In
cmd/root.go we can see that the
init function calls
cobra.OnInitialize(initConfig), which means that whenever a command is executed or invoked, it executes all the functions in the
init function before executing the
execute method. This initialization can be used for loading configuration files or for constructors and so on, depending entirely on the real situation of our application.
function inside the initialization function, so that when therootCmd
execution methodRUN: func
is run, therootCmd
root command will first run theinitConfig
function, and when all the TheRUN: func
execution function ofrootCmd` will be executed only after all the initialization functions have been executed.
We can add some Debug information to the
Then, again, reconstruct and execute.
You can see that the information inside the
initConfig function is run first, and then the actual execution of the function is done.
In order to understand the whole CLI execution flow, we add some debug information to
main.go as well.
Then, again, reconstruct and execute.
Based on the above log information, we can understand the flow of CLI commands.
The last thing the
init function deals with is
Flags are similar to command identifiers, we can think of them as conditional operations of some kind, two types of identifiers are provided in Cobra:
Persistent Flags and
Persistent Flags: This flag can be used for the command to which it is assigned and for all subcommands of that command.
Local Flags: This flag can only be used for the command to which it is assigned.
This function is used to set a configuration file named
.my-calc in the home directory, which will be used if the file exists.
viper is an excellent Golang library for solving configuration files. It can read information from JSON, TOML, YAML, HCL, envfile, and Java properties configuration files, which is very powerful and goes beyond reading configuration. Introduction: https://github.com/spf13/viper.
Now that we can remove some of the print statements we added earlier, we’ve created a
my-calc command as a
rootCmd command that will print the
Hello Cobra CLI message when we execute the root command, so let’s add some other commands to our CLI application.
Create a command named
add in the root of the project. The way
Cobra adds a new command is:
cobra add <commandName>, so we’ll do it here directly like this.
Now we can see that a new
add.go file has been added to the
cmd/root.go file, and if we look closely we can see that the file is relatively similar to
function with a*cobra.Command` pointer and a string slicing parameter.
Then it is initialized in the
init function, and after initialization, it is added to the
rootCmd root command
rootCmd.AddCommand(addCmd), so we can think of
addCmd as a subcommand of
Again, now rebuild the application and execute :
We know that the
RUN function takes the user string slice as an argument, so to support adding numbers, we first need to convert the string to int type and return the calculation result.
Add a function named
intAdd to the
cmd/add.go file, defined as follows
Then, in the
addCmd variable, update the
RUN function to remove the default print message and call the
addInt function declared above.
Then rebuild the application by executing the command shown below.
args argument in the
RUN function is a string slice, we can pass any number of arguments, but it does have the drawback that it can only compute integers, not decimals. For example, if we perform the following calculation, we will just panic:
Since inside the
intAdd function we are only converting strings to int, not float32/64 types, we can add a
flag identifier to the
addCmd command to help the CLI determine whether it is an int calculation or a float calculation.
init function in the
cmd/add.go file, we create a local identifier of type Bool, named
float, abbreviated to
f, with a default value of false. this default value is very important, meaning that the value of the flag identifier will be false even if it is not called on the command line.
Then create a
This function is almost identical to the
intAdd function above, except that it converts a string to float64. Then in the
RUN function of
addCmd, we determine whether
floatAdd should be called based on the passed identifier, and if the
-f flags are passed, the
floatAdd function will be called.
Now recompile the build CLI application and execute it as follows.
Then we extend it by adding some subcommands to
Adding even numbers
Also add a command named
even to the root of the project with the following command.
As above, a new file named
even.go will be added under the
root directory. Modify the
init function in this file, and change
addCmd, since we are adding a subcommand for
Then update the
RUN function of the
evenCmd structure parameter:
First convert the string to an integer, then determine if it is even before adding it up. Then recompile and build the application:
my-calc is our root command,
add is a subcommand of
even is a subcommand of
addCmd, so call it in the same way as above. You can add another subcommand for adding odd numbers in the same way.
Here we have created a simple CLI application in Golang using
Cobra. This article is relatively simple, but it’s a good way to get started learning the basics of
Cobra, and we can try to add some more complex use cases later.