Develop a DIY progress bar

Channels and Routines

Go offers so-called goroutines and channels (as discussed in a previous issue [4]) for firing off concurrent program parts and their synchronization. Listing 2 shows how to illustrate the long copying process for a large file in Go with a progress bar from the termui package. After installing the UI package like this

$ go get

and building the program like this

$ go build cpgui.go

the call to cpgui foo copies a file named foo to foo.bak. If it is relatively large, you can follow along by watching the terminal UI's progress bar drawn on the terminal, gradually growing to the right until it reaches the end of the display element, at which point the file has been copied and the program terminates (Figure 4).

Line 19 of Listing 2 defines a new structure using NewGauge() from the termui package; this represents the UI element of a progress bar. The initial value of the bar growing from left to right is set to   with the Percent attribute; in other words, the bar is at zero percent and thus completely on the left and invisible. Lines 21 to 26 define further attributes, such as the colors of the bar's individual components, their size, or the caption for the graphic element.

Communication Channels

Listing 2 uses the channels defined in lines 29 and 30, named update and done, for the communication between the different program parts that write the data or refresh the bar.

The backup() function as of line 56 receives the update channel as a parameter from the main program and uses it to update the progress bar by sending the completed percentage into it, as soon as it has written a chunk of bytes to the file. On the receiving end of the channel, the main() function waits for new messages in a concurrent goroutine running an infinite loop starting in line 40. The read function on the channel in line 41 blocks if no new values have been written to the channel yet by the backup() function. When a new integer value arrives in the channel, line 41 updates the progress bar's Percent attribute in g to the new percentage value and then redraws the graphic element with ui.Render(g).

The second channel, done, allows the backup() function, which also receives this channel as a parameter from the main program, to initiate the end of the program. For this to happen, the main function asynchronously waits for data in the done channel in a goroutine starting in line 33. As soon as data becomes available (because lines 60, 70, or 84 have sent a true into the channel), line 35 triggers ui.StopLoop() in order to close the UI event loop, which tears down the GUI in line 53 and terminates the main() function.

In this simplified example, the ReadFile() function reads the source file data via the ioutil package in one fell swoop. If the package isn't installed yet, you do so by typing go get io/ioutil. The data ends up in an array slice holding elements of the Byte type. The len() function in line 64 determines the length of the file in bytes and stores it in the total variable. Line 67 creates the new file with the .bak extension on the filesystem and returns a writer interface in out. The interface is then passed onto the Write() function, which sends 4096-byte chunks in line 78 until all the bytes of the original file have been successfully copied. Reading all data into memory at once is obviously not a good idea for large files; in a real-world application, you'd want to read the data in chunks and write it as it becomes available, updating the progress bar accordingly.

In the write loop, the next 4096-byte long chunk from the buffer input is retrieved by the

chunk, input = input[:lim], input[lim:]

statement in line 77 and dumped into the chunk array slice, while simultaneously erasing the data from the original input buffer. Since Go's array slices are just lightweight constructs referencing underlying arrays (which won't change in this case), this is actually very efficient.

The loop starting in line 76 repeats until only a remainder with less than 4096 bytes is left in the input array slice; line 82 writes the rest to the new file outside the For loop, thus completing the copy process.

To allow the user to interrupt the copy process manually if necessary, line 49 defines a keyboard handler for the Q key, which uses StopLoop() to close the GUI and terminate the program cleanly.

At the end of the copy process, line 84 sends a true value to the done channel, which causes the main program in line 34 immediately to unblock and proceeds to terminate the program via ui.StopLoop().

One thing to be aware of is that entertaining the user during the copy process is expensive: Writing data in 4096-byte blocks slows things down considerably in the case of larger files. And, as noted previously, Listing 2 reads the file contents to be copied into memory in a single action, which may not be a good idea for gigabyte-sized masses of data. But it's definitely good enough for illustrative purposes, such as for Hollywood's film industry.


  1. Progress bar:
  2. Listings for this article:
  3. "Programming Snapshot – Classics Repackaged: termui" by Mike Schilli, Linux Magazine, issue 218, January 2019, p. 42
  4. "Programming Snapshot – Simulatenous Runners: goroutines" by Mike Schilli, Linux Magazine, issue 219, February 2019, p. 40

The Author

Mike Schilli works as a software engineer in the San Francisco Bay area, California. Each month in his column, which has been running since 1997, he researches practical applications of various programming languages. If you email him at he will gladly answer any questions.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Ready to Rumble

    A Go program writes a downloaded ISO file to a bootable USB stick. To prevent it from accidentally overwriting the hard disk, Mike Schilli provides it with a user interface and security checks.

  • Book Collector

    Mike Schilli does not put books on the shelf; instead, he scans them and saves the PDFs in Google Drive. A command-line Go program then rummages through the digitized books and downloads them as required.

  • Magic Cargo

    To be able to power up and shut down his NAS and check the current status without getting out of his chair, Mike Schilli programs a graphical interface that sends a Magic Packet in this month's column.

  • Quick and Easy File Transfer with netrw

    Even without elaborate infrastructure, you can still push your data across the network with netrw.

  • Let's Go!

    Released back in 2012, Go flew under the radar for a long time until showcase projects such as Docker pushed its popularity. Today, Go has become the language of choice of many system programmers.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More