Make Your Scripts User Friendly with Zenity
The first time I played with Zenity, I recognized several potential uses for it. While I'm pretty comfortable with interacting with computers with a command line interface, I know many people are not. Zenity creates GUI widgets from a simple command line and can be used from any shell script. This allows an Administrator to write a shell script that performs a given function but make the program easy for less sophisticated users to interact with.
There are many times when you need to perform a repetitive task but you don't want to expose your users to the shell prompt. As much as I don't like having to point at things with a mouse in order to get a computer to do what I want, I understand that sometimes, using a GUI can reduce the chance of making a simple mistake. For example, if a program needs the user to enter a date, does it want the date in YYYY-MM-DD format, or MM-DD-YY format? Your program could tell the user how it expects the date,, but why make things more complicated than they need to be? Why not simply let the user select the date from an interface they are familiar with, a calendar? When was the last time you mistyped a long filename that that had mixed upper and lower case characters? Obviously, it would be easier to point and click on the filename instead.
With Zenity, your program can display calendar, file selection, text input and text message widgets. The results of the user's interaction with these widgets are then available to the shell script.
Let's take a look at a few simple examples before moving on to the real world example.
The following command will present the user with a calendar with “Example 1” in the title bar and the text, “When is your birthday?” at the top of the window. When the user selects a date, Zenity prints the selected date to STDOUT, which is then assigned to the variable, birthday.
export birthday=`zenity --calendar --text='When is your
birthday?' --title='Example 1'`
Users have learned what an error window means without even having to read the text and Zenity allows your scripts to give intuitive error indications. Simply use a command such as this.
zenity --error --text='Something very bad has happened!' --title='Example 2'
As you can see, Zenity is pretty easy to use. You simply tell it which widget you want it to display and then customize that widget using various other parameters. Then you can accept the results, if any, by reading Zenity's STDOUT.
Let's consider a hypothetical situation that arises quite often in business. You perform daily backups of your user's home directories and they frequently delete files and need you to recover them from backup. The problem is that you don't want to be bothered with simple tasks like that, but you don't want to have to train all your users to perform their own file recovery. Instead, like most Linux Administrators, you write a script.
Here is a quick script I wrote as an example of a backup file recovery program.
#!/bin/bash
export repository="/tmp/backups"
export user=`whoami`
############# Step 1 #########################
zenity --info --title="File Recovery Program" --text="You are about to be
asked for the date of the backup file you wish to recover from. Select the
date to continue."
############# Step 2 #########################
export date=`zenity --calendar --date-format=%Y-%m-%d`
if [ ! -f ${repository}/${user}/${date}.tgz ] ; then
echo No such file ${repository}/${user}/${date}.tgz
zenity --error --text="I\'m sorry, the backup for $date wasn\'t
found."
exit;
fi
############# Step 3 #########################
export files=`tar -tzf ${repository}/${user}/${date}.tgz |
zenity --list--title "Select the files to
recover" --column "Files" --separator=" " --multiple`
############# Step 4 #########################
zenity --info --title="File Recovery Program" --text="Next, you must select
which directory to recover your files to"
export target_dir=`zenity --file-selection --directory`
############# Step 5 #########################
zenity --info --title="File Recovery Program" --text="If you would like, I can
create a new directory for your recovered files to be saved to. If you would
like this, enter the name of the new directory next. Otherwise, just click on
OK"
export dir=`zenity --entry --title="File Recovery Program"`
############# Step 6 #########################
if [ -f ${target_dir}/${dir} ] ; then
zenity --error --text="I can not create a directory named
${target_dir}/
${dir} because there is a file with the same name already there"
exit
fi
if [ ! -d ${target_dir}/${dir} ] ; then
mkdir ${target_dir}/${dir}
fi
############# Step 7 #########################
tar -xzvf ${repository}/${user}/${date}.tgz -C ${target_dir}/${dir}/ $files |
zenity --progress –pulsate
zenity --info --title="File Recovery Program" --text="File recovery complete."
I've broken the script into 7 parts to make it easier to discuss what each part of the script does.
The script starts by defining a few important variables. Since this is a fictional scenario, I defined by backup repository to be in /tmp/backups; you'd probably put your backups elsewhere. Then we start step one of the user dialog.
Step one is pretty intuitive. It simply tells the user what to expect from the next dialog. When the user clicks on the OK button, we go to step tow and the user is presented with a calendar. The user picks the date from which to recover the lost files. The program line that displays the calendar uses a format string to tell Zenity how to format the resulting date. I wanted the date to be in a sortable format, so I chose YYYY-MM-DD. I replaced the default slash character with simple dashes because otherwise, the shell would think YYYY/MM/DD referred to 3 directory levels, when we really want it to mean a single filename. The format string is exactly like those used in date(1).
Step 2 then checks to see if the backup file actually exists. If it does not, then it displays an error message and terminates. Otherwise, we continue to the next step in the script.
In step 3, we send the output of the tar command to Zenity so that our user can select which files they want to restore from the tarball. We use the --multiple parameter so that they can select more than one file to recover. Also notice that we set the --separator to a single space. By doing this, we cause Zenity to produce a list of filenames for recovery and we can use that list as a single variable substitution on the command line.
Next we want to give the user the chance to restore their files to a directory other than their current working directory. We use the --file-selection widget for this purpose. By using the --directory parameter, we restrict the user to only being able to select directories, not files.
In step 5, we tell the user that the program can create a subdirectory in the directory they just selected and all of the recovered files will go into that subdirectory. Then we use the --enter widget to let the user tell us what subdirectory to create. If the user presses the OK button without entering anything, our script will simply restore the files into the selected directory.
In step 6, we do some error checking to make sure there isn't already a file with the same name as the subdirectory the user entered. If there is, we give the user an error message and our program terminates.
Then we make the subdirectory if it doesn't already exist.
Finally, in step 7, we recover the user's files from the selected tarball and place them in the appropriate directory, while giving the user visual feedback in the form of a progress bar. This way, our user doesn't need to guess if the program is working, and they aren't confronted with a list of filenames, or even worse, error messages.
As you can see, Zenity makes it very easy to write shell scripts that interact with the user via the GUI. Admittedly, this example was a little contrived. The example was meant to be rich enough to demonstrate as many of Zenity's features as possible while still appealing to practicality. It wouldn't take many changes to have the recover script use ssh and scp to access a remote backup repository, perhaps at a remote location. The user wouldn't have to know any of the details since all they ever see is a nice user-friendly series of dialog boxes.
Most Linux Administrators are comfortable with the command line interface and many might think that using Zenity to “dumb-up” a simple shell script is a waste of time. However many Linux Users aren't as proficient with the shell interface and will appreciate your efforts to make their work as “point and click” as possible.