Building CLI-Based File Renaming Tool With Golang
With the capability to build lightweight yet efficient system, Golang has been getting a lot of attention from software engineers who use it to build backend services, microservices etc as well as DevOps engineers who use it as scripting language. FYI, Kubernetes is written in Go as well.
In this article, we will be looking at how to build a CLI-based tool to rename files in batch using spf13/cobra. While there are other libraries that can be used to build CLI application, Cobra offers the usage of flags, arguments validation, auto-generated help command, suggestion when there is typo and many more.
Let’s get start on building our file renaming tool. Before diving into the code, let’s lay out the requirements:
- Ability to specify the target folder
- Ability to scan all files in subfolders
- Ability to include or exclude files based on name or extension
Root command
First, we start by defining a root command which will be called in the main.go
. Also, we can create a folder called cmd
where we can store all the command files.
To execute the root command from main.go, we can add the following line in the main()
function:
cmd.RootCmd.Execute()
With just a few lines, we can already see the CLI application in action. Simply run go run main.go
and we will see auto-generated help message on the root command similar to the picture below:
Sub command
Next, we can add more commands to the root command. For example, in the code below, we are adding sub command rename
so we can execute commands as such filezy rename
.
Cobra comes with built in validators that check on the arguments on the command. In the code excerpt above, cobra.ExactArgs(1)
ensures that there is only 1 positional argument and throws error if validation fails. One (1) number of argument is exactly what we need where we allow users to key in the new file name.
renameCmd.Flags().StringVarP(&folder, "folder", "f", "./", "Target folder to be scanned")
Cobra also provides simple way to add flags. In the example above, we are adding a flag -f
to the renameCmd
with default value as ./
. To specify a different folder like /c/folder
, we can trigger the command as such: filezy rename [filename] -f /c/folder
.
Read files from folder
After specifying a flag to read the folder input, let’s implement the logic to read files from the folder.
First, it opens the folder in os.Open(folder)
then the Readdir(-1)
function returns all the FileInfo from the folder in slice. We then loop through all the FileInfo in the slice and append it to files
array if it is a folder. The file name will be used later for renaming purposes.
Read files from subfolder recursively
While the above codes can read the files in the current folder and we can possibly call the function to read the directory again when it hits the subfolder, it involves a lot of codes and this can be solved using path/filepath
standard library.
The walk function walks the file tree starting from the folder given (as root) into each file or directory in the tree. Note that it can be quite slow when it comes to a large directory.
Adding filter
Not all of the files scanned shall be renamed and that’s why it’s advisable to add a filter to include or exclude files based on the file name or extension. There are several filters that I can think of, for example based on prefix, suffix, regex pattern and extension.
To ease the process, it will be easier that we can store the file name in separate fields such as base file name and extension. Again, with the use of flags, we can get the input of prefix, suffix, regex or extension.
strings
and regexp
are the two standard library that provides string comparison on prefix and suffix and string matching based on regex pattern.
Numbering of the files
As we are renaming the files in batch where they can potentially be overwritten, we should come up with a mechanism to add an auto-increment number as suffix to the file name.
Besides that, we should keep the sequence of the files the same as before. To achieve this, simply appending number will mess up the sequence of the files. To give you an example, assuming that there are 20 files to be renamed and the names will be as such: file-1.jpg, file-2.jpg, … … file-10.jpg, file-11.jpg… … file-20.jpg. As a result, file-10.jpg will be sorted before file-2.jpg.
To avoid the sequence being reordered, we can add zero in front of the numbers to standardize the number of digits of the suffix. In the code excerpt below, we create two helper functions: to calculate the number of digits of the total number of files and to get the auto-incremental suffix number.
Renaming the files
With the old file name and newly built file name, we can call the following function to rename the files.
os.Rename(<old file name>, <new file name>)
Do note that if there is existing file name that coincides with the new file name, then the existing file will be replaced.
Build and run
We have come to an end and we can build the Go application by running go build
. We can then add the folder where the built application is to the PATH so that we can run it from the command prompt.
Source code: https://github.com/wilsontwm/filezy
Thanks and cheers!