Search

Linux - How to Cancel a Zenity Progress Dialog the right way

Contents[Hide]

dropcap linux

When you write shell scripts to run some long processing actions, Zenity and its --progress option might be a nice way to trace and follow the execution of your sequenced actions.

By default, Zenity offer a Cancel button to interrupt the process.

ubuntu zenity progress

Normal behavior of Zenity when you hit the Cancel button is to kill the progress dialog process & the function before the pipe. But it doesn't kill any child process of this function running in the background.

You can use the --auto-kill option to kill all the processes, but then you will kill your main script process and all its children. That means that it will kill your entire script, including any action after the zenity dialog.

This article explains how to write a bash script with Zenity progress dialog that will properly handle the Cancel action of a Zenity progress dialog where :

  • any action run by the function piped with zenity will be killed
  • you'll get back to your main script to run next steps

The procedure has been written and tested under Ubuntu 14.04 LTS, but it should be applicable to any modern Linux system as it uses only basic system tools.

1. Problem Description

A typical bash script running a Zenity progress dialog looks something like this :

myscript
#!/bin/bash

...

(
  echo "# running command 1"
  command1 ...

  echo "# running command 2"
  command2 ...
) | zenity --title="my specific title" --progress --pulsate --auto-close

next-command

...

First thing you need to know is that zenity only gets triggered when a button is pressed if the supervised running process (within the function ( ...) ) sends something to the pipe |.

It means that the zenity dialog will only be able to handle your Cancel action as soon as command1 or command2 are displaying some informations on the console.

If they don't display anything for a long time, the zenity dialog box won't get informed that you've pressed the Cancel button.

So, it is very important to make sure that whatever command you launch in the function piped to zenity  is verbose enough for zenity to be reactive to a button pressed.

To well understand what is really happening, lets see what process tree looks like for previous script :

|
|- myscript (1)    [ main script ]
        |
        |- myscript (2)     [ process created to run the function ( ... ) ]
        |       |
        |       |- command1
        |       |- command2
        |
        |- zenity               [ zenity progress dialog ]
        |
        |- next-command

Because of the --auto-close option, as soon as command1 and then command2 will be finished, zenity progress dialog will be closed and your main script myscript (1) execution will carry on.

If you Cancel zenity dialog during command1 execution, it kills zenity and myscript (2). But command1 is not killed.

Of course, as myscript (2) has been killed, command2 will never be executed. But as command1 was running, it has become orphan and has been attached to the process init --user.

The process tree will then looks like this :

|
|- myscript (1)    [ main script ]
        |
        |- next-command

|- init --user
        |
        |- command1

The problem is now that if command1 is a very long process, it still keep running a long time after you've cancelled the progress dialog box.

But as myscript (2) has been killed, it is then impossible to get its child process ID to kill command1.

2. Solution Proposed

To be able to kill whatever action is running as soon as you Cancel the progress dialog, you need to keep track of all the children PID of myscript (2).

If you keep this list updated, you'll be able to kill any task running at the time you cancel the dialog.

So, the approach proposed can be descibed in these steps :

  1. Run zenity and go back immediatly to the main script myscript (1).
    This is done by running zenity with & at the end of the command
  2. Then, get zenity dialog PID (zenity)
    Zenity is the last process created by the main script and is accessible with ${!}
  3. From current myscript (1) process ID, get its firstly created child PID  (myscript (2))
  4. Run a loop as long as zenity dialog is open (PID present)
    In this loop, you update periodically the list of grand-children PID (command1 and then command2)
    These processes are direct children of child PID
  5. As soon as the loop ends, it means that zenity dialog box has been closed (cancelled or not)
  6. If the list of grand-children PID  is not empty, zenity dialog as been cancelled
    You then need to kill all these processes
  7. You are now ready to carry on with the execution of next-command in myscript (1)

Even if at first sight this procedure seems a little bit complex, its implementation is quite easy.

Implementation for previous script can be done like this :

myscript
#!/bin/bash

...

(
  echo "# running command 1"
  command1 ...

  echo "# running command 2"
  command2 ...
) | zenity --title="my specific title" --progress --pulsate --auto-close &

# get zenity process id
PID_ZENITY=${!}

# get firstly created child process id, which is running all tasks

PID_CHILD=$(pgrep -o -P $$)

# loop to check that progress dialog has not been cancelled
while [ "$PID_ZENITY" != "" ]
do
  # get PID of all running tasks
  PID_TASKS=$(pgrep -d ' ' -P ${PID_CHILD})

 
# check if zenity PID is still there (dialog box still open)
  PID_ZENITY=$(ps h -o pid --pid ${PID_ZENITY} | xargs)

  # sleep for 2 second
  sleep 2
done

# if some running tasks are still there, kill them

[ "${PID_TASKS}" != "" ] && kill -9 ${PID_TASKS}

next-command

...

As you can see this script structure is simple and non intrusive. You keep your script structure intact, just adding a control loop.

It should allow you to fully handle cancellation of your progress dialog box in a straight forward and efficient manner.

 

Hope it helps.

Signature Technoblog

This article is published "as is", without any warranty that it will work for your specific need.
If you think this article needs some complement, or simply if you think it saved you lots of time & trouble,
just let me know at This email address is being protected from spambots. You need JavaScript enabled to view it.. Cheers !

icon linux icon debian icon apache icon mysql icon php icon piwik icon googleplus