1

I am displaying a command progress in a bash script. The command output is piped to zenity --progress and can eventually run for a long time. I want to abort it (kill that command) if I cancel the zenity dialog:

( echo 0; command; echo 100 ) | if ! zenity --progress
                                then DO_SOMETHING_TO_KILL_command
                                fi

All the solutions I have found either:

  1. generically kill "command" with pgrep, pidof, pkill, killall, etc. This is not what I want since there might be many such "command" running.

  2. create a fifo to output the PID of "command" (command & echo $! >some_fifo; wait) and then read from it after the pipe.

Solution 2. does what I want, but in an overcomplicated way (see e.g. an example here (in French)). I want to avoid fifos or temp files if possible. It seems as if it could be done with an output redirection to a file descriptor at most, but I cannot figure exactly how.

Note: command substitution for the whole group with redirection e.g., $( ( ... command& echo >&3 ... | zenity --progress ) 3>&1 ) -- which is a generic soution in cases of this type -- does not work here because the $(...) waits until the whole subshell is completed.

oriol
  • 33
  • 1
  • 5
  • the 'echo 0' is only needed in case we run 'zenity --progress --pulsate'; when 'command' does not output numbers, it often looks nicer this way. – oriol Mar 07 '13 at 08:56

3 Answers3

1

One of the most robust implementations:

#!/bin/bash

( # the pipe creates an implicit subshell; marking it explicit
 (
  sleep 10 & echo $! >&3
  wait
  echo 100 # to stdout, first pipe
 ) | (
  if ! zenity --progress
  then echo zen_progress_aborted >&3
  fi
 )
) 3>&1 | (
      read FD3_FIRST
      read FD3_SECOND
      # the PID comes first and the zenity output second almost always
      #+but we cannot be sure so:
      if [ "$FD3_SECOND" = "zen_progress_aborted" ]
       then kill $FD3_FIRST
      elif [ "$FD3_FIRST" = "zen_progress_aborted" ]
       then kill $FD3_SECOND
      fi
     )
# 'echo 100' after 'wait' means zenity does not exit after the command ends
#+so no need to kill it in this case

exit 0

It is significantly more complex than using 'coproc' but it is POSIX portable and it is robust in the rare case where the second 'echo' overtakes the first one (e.g., zenity fails because of a bug and the subprocess happens to start later)

oriol
  • 33
  • 1
  • 5
1

A simple, POSIX version:

( # the pipe creates an implicit subshell; marking it explicit
 ( sleep 10; echo 100 )& echo $!
) | (
 read PIPED_PID; zenity --progress || kill $PIPED_PID
)

which works also if zenity fails. Even removing the first command (sleep 10 in this case), the echo $! will always output first to the pipe, so we read it just before running the progressbar.

An even simpler variant of this when the long command (sleep 10 above) does not output progress numbers:

sleep 10 & PIPED_PID=$!
tail -f /dev/null --pid $PIPED_PID | ( zenity --progress || kill $PIPED_PID )

wait does not work here because the pipe makes a subshell that is sibling of the & subprocess. This works on shells other than bash, but tail --pid is not POSIX standard. A POSIX version of this:

sleep 10 & PIPED_PID=$!
while kill -s 0 $PIPED_PID; do sleep .1; done | ( zenity --progress || kill $PIPED_PID )
oriol
  • 33
  • 1
  • 5
0

This should work:

coproc { echo 0; command; echo 100; }
zenity --progress <&${COPROC[0]} || kill $!
oriol
  • 33
  • 1
  • 5
djoot
  • 39
  • 1
  • Very nice, compact solution. Only: 1) It is fragile in the sense that the second line must be run before the co-process ends. A tiny wait between lines breaks the <${COPROC[0]} part. 2) It is not POSIX. While it is ok for bash, I wonder if there is a POSIX compatible solution. – oriol Mar 07 '13 at 14:40
  • Another problem of coproc is that <&${COPROC[0]} [cannot be redirected to a subshell](http://stackoverflow.com/questions/10867153). I want to run some commands if zenity successes and others if it fails, but I have to do this without spawning a subshell. – oriol Mar 07 '13 at 17:14
  • the redirection to subshell problem is rather simple to overcome: exec 3<&${COPROC[0]} and then ( subshell )<&3 – oriol Mar 07 '13 at 18:34