60

If I start a script that is going to take a long time, I inevitably realize it after I've started the script, and wish I had a way of doing some kind of alert once it finishes.

So, for example, if I run:

really_long_script.sh

and press enter...how can I run another command once it finishes?

5 Answers 5

73

You can separate multiple commands by ;, so they are executed sequentially, for example:

really_long_script.sh ; echo Finished

If you wish to execute next program only if the script finished with return-code 0 (which usually means it has executed correctly), then:

really_long_script.sh && echo OK

If you want the opposite (i.e. continue only if current command has failed), than:

really_long_script.sh || echo FAILED

You could run your script in a background (but beware, scripts output (stdout and stderr) would continue to go to your terminal unless you redirect it somewhere), and then wait for it:

really_long_script.sh &
dosomethingelse
wait; echo Finished

If you have already run script, you could suspend it with Ctrl-Z, and then execute something like:

fg ; echo Finished

Where fg brings the suspended process to foreground (bg would make it run in background, pretty much like started with &)

7
  • @mlissner Expanded my answer to cover this case
    – aland
    Sep 11, 2011 at 6:29
  • 10
    Since fg returns the value of the job it resumes, you can use fg && echo Finished if you want to ensure the command succeeds before executing the other command.
    – palswim
    Jun 24, 2017 at 5:35
  • Ctrl+Z stops a process (for me at least?). fg seems to start the same command again. In the case of youtube-dl or some other extremely well written programs, that might work, but not for most. Jun 22, 2020 at 19:08
  • 2
    @FabianRöling Ctrl-Z pauses a process (by sending SIGHUP), and bg or fg later resumes it (not starts it again). You can try launching some GUI application from a terminal, then press Ctrl-Z in terminal (the app will freeze), then do fg (the app will become responsive again). I never encountered any issues despite extensively using Ctrl-Z for many years for a variety of programs. It messes up some fancy screen output (e.g., progressbar), but that is to be expected since you temporary take control of the terminal.
    – aland
    Jun 25, 2020 at 15:57
  • 2
    @FabianRöling Yes, it says "stopped". But you can call jobs in the same console to see that the process is still there (albeit, indeed, in a paused/stopped state). You can also call ps a and see that the process still exists. It's stopped, but not terminated, and can be resumed (by fg or bg). If you try to close the console by typing exit (instead of closing the terminal window) it will complain about the stopped process.
    – aland
    Jul 1, 2020 at 17:13
42

If the process does not run on current tty, then try this:

watch -g ps -opid -p <pid>; mycommand
1
  • 5
    I love the creative use of watch. Super short answer and one learns a new useful way of using watch Feb 13, 2019 at 9:51
20

You can also use bash's job control. If you started

$ really_long_script.sh

then press ctrl+z to suspend it:

^Z
[1]+  Stopped                 really_long_script.sh
$ bg

to restart the job in the background (just as if started it with really_long_script.sh &). Then you can wait for this background job with

$ wait N && echo "Successfully completed"

where N is the job ID (probably 1 if you didn't run any other background jobs) which is also displayed as [1] above.

1
  • 1
    Ensure you prepend N with # like in wait #1 (otherwise waiting on process id 1) Dec 13, 2022 at 9:16
4

Turns out this isn't that hard: You can simply type the next command into the window while the existing one runs, press enter, and when the first one finishes, the second command will automatically run.

I'm guessing there are more elegant ways, but this does seem to work.

Editing to add that if you want to run an alert after the command finishes, you can create these aliases in .bashrc, then run alert while it is running:

 alias alert_helper='history|tail -n1|sed -e "s/^\s*[0-9]\+\s*//" -e "s/;\s*alert$//"'
 alias alert='notify-send -i gnome-terminal "Finished Terminal Job" "[$?] $(alert_helper)"'
2
  • 10
    Unless the running script expects input at one point.
    – Daniel Beck
    Sep 11, 2011 at 5:59
  • @Daniel - yeah...that's a problem. Shoot.
    – mlissner
    Sep 11, 2011 at 6:07
1

A while ago I have written a script to wait for the end of another process. NOISE_CMD could be something like notify-send ..., given that DISPLAY is set correctly.

#!/bin/bash

NOISE_CMD="/usr/bin/mplayer -really-quiet $HOME/sfx/alarm-clock.mp3"

function usage() {
    echo "Usage: $(basename "$0") [ PROCESS_NAME [ PGREP_ARGS... ] ]"
    echo "Helpful arguments to pgrep are: -f -n -o -x"
}

if [ "$#" -gt 0 ] ; then
    PATTERN="$1"
    shift
    PIDS="$(pgrep "$PATTERN" "$@")"

    if [ -z "$PIDS" ] ; then
        echo "No process matching pattern \"$PATTERN\" found."
        exit
    fi

    echo "Waiting for:"
    pgrep -l "$PATTERN" "$@"

    for PID in $PIDS ; do
        while [ -d "/proc/$PID" ] ; do
            sleep 1
        done
    done
fi

exec $NOISE_CMD

Without any argment, just make some noise immediately. This behavoir allows something like this for convenience (say you call script below alarm.sh):

apt-get upgrade ; ~/bin/alarm.sh

Of course you can do many funny things with such a script, like letting an instance of alarm.sh wait for an instance of alarm.sh, that is waiting for some other command. Or executing a command just right after some task of a other logged in user has finished... >:D

A former version of the script above might be interesting, if you want to avoid a dependency on pgrep and accept to lookup process IDs yourself:

#!/bin/bash

if [ "$#" -lt 2 -o ! -d "/proc/$1" ] ; then
  echo "Usage: $(basename "$0") PID COMMAND [ ARGUMENTS... ]"
  exit
fi

while [ -d "/proc/$1" ] ; do
  sleep 1
done

shift
exec "$@"

Slightly off-topic, but useful in similar situations: One might be interested in reptyr, a tool that "steals" a process from some (parent) shell and runs it in from current shell. I have tried similar implementations and reptyr is the prettiest and most reliable for my purposes.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .