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.

______________________

Mike Diehl is a freelance Computer Nerd specializing in Linux administration, programing, and VoIP. Mike lives in Albuquerque, NM. with his wife and 3 sons. He can be reached at mdiehl@diehlnet.com

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

formating text

Michael Jensen's picture

im realy happy with zenity, and i have found the
\n =new line
\r =carrige return
\t =tab input
is there anny more options i can use to format text in a zenity dialog? i would like things like bold text, textsize and so on

Zenity --list with variables

Florin Norberg's picture

zenity --list --column 1 --column 2 --column 3 \
one two "three four" five six "seven eight"

The example above puts "three four" in column 3 as one phrase as well as "seven eight" in column 3 as one phrase.

blah="one two \"three four\" five six \"seven eight""
zenity --list --column 1 --column 2 --column 3 $blah

This example doesn't behave quite the same. I've tried this 50,000 ways and can't get it to work. Any ideas?

eval is your friend: eval

Cigy Cyriac's picture

eval is your friend:

eval zenity --list --column 1 --column 2 --column 3 $blah

will do what you want. You need this to process quotes within quotes properly.

BTW, you can simplify setting blah this way:

blah="one two 'three four' five six 'seven eight'"

Another zenity Question

Florin Norberg's picture

Is there an item limit on zenity --list --checklist dialogs?? I had a script that was supposed to display a bunch of files for the user to select from via a zenity --list gui. However I've never been able to get them all to show up.

Quick example:

I ran this in a directory for testing purposes and tries to get zenity to show me all the files in a list.

for i in $(seq 1 44)
do
touch $i.txt
done

The command
"ls | zenity --list --checklist --column pick --column file --multiple"
will never show all 44 files, in fact i don't think it displays more than maybe 20. Is there some way around this?

Old Version Maybe?

Mitch Frazier's picture

I'm using zenity version 2.28.0 and I ran the above command in a directory with 500+ files and it shows all the files.

Mitch Frazier is an Associate Editor for Linux Journal.

Zenity --list

Florin Norberg's picture

I've found that it works if you don't use the --checklist option and you use only one column. I'd still like your help with the --checklist option though if you have a more elegant solution.

Thanx

It works with checklist too

Frank Schenk's picture

Just make sure you deliver the right data

$files = $( ls -1 );

result=$( zenity --list --checklist --column pick --column file --multiple $files $files );

echo $result

cheers, Frank

WIndow Position

Florin Norberg's picture

Is there a way to change the window position of a zenity gui?

Window Position

Mitch Frazier's picture

There doesn't appear to be a way to do set the window position. Normally one could use the -geometry option, but that produces a "This option is not available" error when you try to use it.

Mitch Frazier is an Associate Editor for Linux Journal.

Excellent Article

Akbar's picture

I am lovin' it :-)

Thank you for this great article.

getting a value from text box

Anonymous's picture

How can i get the value from a text box, if i need the name entered in text box inside my script.how can i get that.. also how to get the item selected in the case of radio selection and multi selection list..?

i will be thankful if i gets ur help..thanks!!

Is it me, or does Zenity lack the ability to show a bitmap?

Fernando Cassia's picture

I see no option in man zenity to show a bitmap. Would have made a great addition. Just show a bitmap (centered) with buttons below to make a choice.

If I'm wrong feel free to correct me.

Thanks,
FC

Exporting variables

Anonymous's picture

Why are you exporting the variables? There's surely no need for that?

Habit

Mike's picture

I usually set my variables in separate files which I then source as needed. I have to export them in that case and over the years it's just become a habit.

Lack of persistence

Donnie Berkholz's picture

I really love the idea of Zenity. My problem is that I can't write a serious, multistep app in it because it doesn't have persistent windows. The only way is to keep popping up new windows all the time, which is very hard to deal with.

It is a shame you can't use

Ryan Roth's picture

It is a shame you can't use glade with scripts like you can with python/

Who said you can't use Glade

Diego Torres Milano's picture

Who said you can't use Glade to design your interface ?
Take a look at autoglade.sf.net, it's an attempt to automate as much as possible the design and program of Gnome/GTK based, cross platform applications whose GUI is designed with Glade.

Links:
http://autoglade.wiki.sourceforge.net/autoglade+tutorial+-+first+steps
http://dtmilano.blogspot.com/2008/09/from-z-to.html
http://dtmilano.blogspot.com/2008/09/lists-meet-autoglade.html
http://dtmilano.blogspot.com/2008/08/introduction-this-article-will-guid...

True indeed.

Anonymous's picture

True indeed.

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState