77

I have a command that I want to have run again automatically each time it terminates, so I ran something like this:

while [ 1 ]; do COMMAND; done;

but if I can't stop the loop with Ctrl-c as that just kills COMMAND and not the entire loop.

How would I achieve something similar but which I can stop without having to close the terminal?

5
  • 11
    If I'm in bash I just use Ctrl-Z to stop the job and then "kill %1" to kill it.
    – Paul Cager
    Jul 4, 2012 at 13:20
  • 4
    Just wait... Linus was quoted as saying: “We all know Linux is great... it does infinite loops in 5 seconds.” -- so really... just wait a few more seconds, it should complete.
    – lornix
    Jul 5, 2012 at 8:40
  • @PaulCager worked for me too! Why does it work where Ctrl-C does not? Mar 15, 2014 at 19:19
  • @cirosantilli it kills the outer job (the bash "wrapper"). In some situations, it won't immediately kill the "COMMAND", for instance, if you background it, it may sneak past alive even if it's parent is dead. But the loop is dead, and that's the important part.
    – orion
    Mar 15, 2014 at 23:01
  • @orion: I don't agree, but I don't exactly know how to proove: Ctrl-C is swallowed by the COMMAND inside the loop, so the COMMAND is interrupted and the outer loop will continue. To kill the outer loop I usually add a sleep command and press CTRL-C some more times: while :; do LONGTIME_COMMAND; sleep 1; done. Please notice: the colon behind while is interpreded as TRUE, so this is an endless loop.
    – ChristophS
    May 5, 2023 at 9:07

11 Answers 11

66

You can stop and put your job in background while it's running using ctrl+z. Then you can kill your job with:

$ kill %1

Where [1] is your job number.

1
47

Check the exit status of the command. If the command was terminated by a signal the exit code will be 128 + the signal number. From the GNU online documentation for bash:

For the shell’s purposes, a command which exits with a zero exit status has succeeded. A non-zero exit status indicates failure. This seemingly counter-intuitive scheme is used so there is one well-defined way to indicate success and a variety of ways to indicate various failure modes. When a command terminates on a fatal signal whose number is N, Bash uses the value 128+N as the exit status.

POSIX also specifies that the value of a command that terminated by a signal is greater than 128, but does not seem to specify its exact value like GNU does:

The exit status of a command that terminated because it received a signal shall be reported as greater than 128.

For example if you interrupt a command with control-C the exit code will be 130, because SIGINT is signal 2 on Unix systems. So:

while [ 1 ]; do COMMAND; test $? -gt 128 && break; done
1
  • 14
    It should be mentioned that this is not guaranteed, in fact, many applications will not do this.
    – Chris Down
    Jul 4, 2012 at 1:18
26

I would say it might be best to put your infinite loop in a script and handle signals there. Here's a basic starting point. I'm sure you'll want to modify it to suit. The script uses trap to catch ctrl-c (or SIGTERM), kills off the command (I've used sleep here as a test) and exits.

cleanup ()
{
kill -s SIGTERM $!
exit 0
}

trap cleanup SIGINT SIGTERM

while [ 1 ]
do
    sleep 60 &
    wait $!
done
2
  • 4
    Nice. Here's how I used this tip to make an autorestarting netcat wrapper: trap "exit 0" SIGINT SIGTERM; while true; do netcat -l -p 3000; done
    – Douglas
    Jan 13, 2013 at 13:00
  • 2
    if you add this trap approach to the same (bash) script with the infinite loop to be killed, use $$ instead of $! (see here)
    – ardnew
    May 22, 2017 at 20:37
16

I generally just hold down Ctrl-C. Sooner or later it'll register between COMMAND's and thus terminate the while loop. Maybe there is a better way.

2
  • 1
    Not sure why, but it fails for certain COMMANDs like paplay on a 1s file. Mar 15, 2014 at 11:07
  • That's the brute force of all solutions here. :/
    – markroxor
    Jan 6, 2019 at 16:46
12

If you run bash with -e it will exit on any error conditions:

#!/bin/bash -e
false # returns 1
echo This won't be printed
1
  • The first line here is by far the simplest solution for a trivial script that you don't wish to spend too much time on!
    – Demis
    Sep 26, 2020 at 19:37
11

Why not simply,

while [ 1 ]; do COMMAND || break; done;

Or when used in a script,

#!/bin/bash
while [ 1 ]; do
  # ctrl+c terminates COMMAND and exits the while loop
  # (assuming COMMAND responds to ctrl+c)
  COMMAND || break
done;
2
  • 2
    Very elegant solution. But wouldn't this only work if COMMAND always returns a success exit status?
    – howard
    Dec 5, 2015 at 1:04
  • Yes @howardh, that's correct. Dec 7, 2015 at 17:29
4

I prefer another solution:

touch .runcmd; while [ -f ".runcmd" ]; do COMMAND; sleep 1; done

In order to kill the loop, just do:

rm .runcmd && kill `pidof COMMAND`
4

What works reasonably well for me is:

while sleep 1; do COMMAND; done

This works because sleep 1 sticks around for a while and if it gets ctrl+c it return with non zero and the loop will terminate.

1
  • 1
    +1, sleep time can also be reduced to 0.5 or 0.1: while sleep 0.1; do COMMAND; done
    – ks1322
    Jun 7, 2020 at 10:35
3
  1. You can always kill a process using its PID, there's no need to close your terminal
  2. If you want to run something in an infinite loop like a daemon then you'd best put it in the background
  3. while : will create an infinite loop and saves you writing the [ 1 ]

    while :; do COMMAND; done &
    

This will print the PID. If you exit your prompt using ctrl+d then the background job won't quit, and you can later kill the job from anywhere using kill PID

If you lose track of your PID, you can use pstree -pa $USER or pgrep -fl '.*PROCESS.*' to help you find it

1

I like this one-liner for this kind of problem:

while $COMMAND ; do : ; done

As long as $COMMAND terminates successfully, it will be run again. If it fails or is terminated by signal, the loop will end.

1
  • 1
    Thank you for using the human-readable if-then syntax rather than the machine-language  ||  or  &&.  You have proved that you are not a robot. Jan 1, 2023 at 3:03
0

Use trap -

exit_()
{
    exit
}

while true
do
    echo 'running..'
    trap exit_ int
done

You must log in to answer this question.

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