374

This answer to Command line command to auto-kill a command after a certain amount of time

proposes a 1-line method to timeout a long-running command from the bash command line:

( /path/to/slow command with options ) & sleep 5 ; kill $!

But it's possible that a given "long-running" command may finish earlier than the timeout.
(Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)

So this nifty 1-liner approach has a couple of problems.
First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable.
Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.

So...

Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that

  • has a bash implementation (the other question already has Perl and C answers)
  • will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
  • will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
  • doesn't have to be a 1-liner
  • can run under Cygwin or Linux

... and, for bonus points

  • runs the tlrbsf command in the foreground
  • any 'sleep' or extra process in the background

such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?

If so, please share your code. If not, please explain why.

I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
system PAUSE
  • 37,082
  • 20
  • 62
  • 59
  • 5
    Another similar question: http://stackoverflow.com/questions/526782/how-do-i-limit-the-running-time-of-a-bash-script (but I think the 'timeout3' answer here is much better). – system PAUSE Mar 27 '09 at 16:23
  • 2
    Any reason to not use the gnu `timeout` utility? – Chris Johnson Mar 27 '18 at 19:52
  • 2
    `timeout` is great! you can even use with **multiple commands** (multi-line script): https://stackoverflow.com/a/61888916/658497 – Noam Manos May 19 '20 at 10:40

24 Answers24

669

You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details. Here's an example:

timeout 5 /path/to/slow/command with options
yingted
  • 9,996
  • 4
  • 23
  • 15
170

I think this is precisely what you are asking for:

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <Dmitry.Golovashkin@sas.com>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $$ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $$ && kill -0 $$ || exit 0
    sleep $delay
    kill -s SIGKILL $$
) 2> /dev/null &

exec "$@"
Matthew H.
  • 302
  • 1
  • 7
Juliano
  • 39,173
  • 13
  • 67
  • 73
  • That's a keen trick, using $$ for the background part. And nice that it will kill a hung tlrbsf immediately. But ugh, you have to choose a polling interval. And if you set the polling too low, it will eat CPU with constant signaling, making the tlrbsf run even longer! – system PAUSE Mar 27 '09 at 00:28
  • 9
    You don't have to choose the polling interval, it has a default of 1s, that is pretty good. And the checking is very inexpensive, the overhead is negligible. I doubt that would make tlrbsf run noticeably longer. I tested with sleep 30, and got 0.000ms difference between using and not using it. – Juliano Mar 27 '09 at 00:36
  • 7
    Right, I see that now. And it meets my exact requirements if you set the poll interval == timeout. Also works in pipelines, works with the whole thing backgrounded, works with multiple instances and other jobs running. Sweet, thanks! – system PAUSE Mar 27 '09 at 01:08
  • 1
    Sending a signal kills the sub-shell, so I thought that lining all kill commands on one line will preserve them. I also enabled stderr output to display unexpected errors. http://stackoverflow.com/questions/687948/timeout-a-command-in-bash-without-unnecessary-delay/14147112#14147112 – eel ghEEz Jan 03 '13 at 20:57
  • 2
    @Juliano That's a great way to handle timeouts, very useful. I wonder if there is a way in which we can have the script return exitcode 143 when the process is killed after timeout? I tried adding "exit 143" right after the kill command, but I always get exit code 0 at the caller script. – Salman A. Kagzi Feb 09 '13 at 08:44
  • Is the '''exec''' at the end required? I would like to use this code as a function, and exec will of course not work in a function because after executing the command, exec does not return. – wackazong Aug 21 '17 at 18:23
  • Yes, `exec` is required since what this script does is set it up for its own "suicide" using `$$` a few times to get is PID, then it replaces itself with the process to be monitored (preserving its PID). – Juliano Aug 22 '17 at 02:49
  • I noticed that it doesn't handle being passed the command `:` well. – Timothy Swan Feb 13 '18 at 13:23
  • I'm having trouble getting `Terminate` feedback. For code: `bash timeout.sh -t 0 gcc helloworld.c |& tee output.txt` I get a console string, but somehow `Terminate` is not piped into output.txt, but I want it to be there as well. – Timothy Swan Feb 22 '18 at 18:53
47

This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.

Examples:

  • your_command runs more than 2 seconds and was terminated

your_command interrupted

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • your_command finished before the timeout (20 seconds)

your_command finished

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
JohnEye
  • 6,436
  • 4
  • 41
  • 67
Dmitry
  • 511
  • 4
  • 3
  • 2
    `wait` returns the exit status of the process that it waits on. So if your command does exit within the allotted time but with a non-zero exit status then the logic here will behave as it it timed out, i.e. print `your_command interrupted`. Instead you _could_ do the `wait` without an `if` and then check if the `$watcher` pid still exists, if it does then you know you didn't timeout. – George Hawkins Apr 20 '15 at 20:27
  • 1
    I can't figure out why this uses `kill` in one case but `pkill` in the other. I needed to use `pkill` for both to make this work correctly. I would expect that if you wrap a command in `()`, then you will need to use `pkill` to kill it. But perhaps it works differently if there is only a single command inside the `()`. – Brent Bradburn Feb 09 '16 at 08:25
  • God bless you sir :) – Darko Miletic Jul 09 '18 at 17:40
35

To timeout the slowcommand after 1 second:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

To determine whether the command timed out or failed for its own reasons, check whether the status code is 124:

# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

Note that when the exit status is 124, you don't know whether it timed out due to your timeout command, or whether the command itself terminated due to some internal timeout logic of its own and then returned 124. You can safely assume in either case, though, that a timeout of some kind happened.

lance.dolan
  • 3,493
  • 27
  • 36
28

There you go:

timeout --signal=SIGINT 10 /path/to/slow command with options

you may change the SIGINT and 10 as you desire ;)

  • 3
    "timeout" is part of **coreutils** package on (at least) Redhat, Centos, Suse and Ubuntu, so you'll need to install that if you don't have it. – Akom Sep 02 '16 at 16:38
  • it's really helpful!!!!!! Do you know why yingted's "timeout 5 /path/to/slow/command with options" sometimes not working? – Decula Jul 19 '18 at 20:11
  • Unfortunately this is not in the `coreutils` package on FreeBSD. – Paul Bissex May 14 '20 at 12:58
21

You can do this entirely with bash 4.3 and above:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Example: _timeout 5 longrunning_command args

  • Example: { _timeout 5 producer || echo KABOOM $?; } | consumer

  • Example: producer | { _timeout 5 consumer1; consumer2; }

  • Example: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Needs Bash 4.3 for wait -n

  • Gives 137 if the command was killed, else the return value of the command.

  • Works for pipes. (You do not need to go foreground here!)

  • Works with internal shell commands or functions, too.

  • Runs in a subshell, so no variable export into the current shell, sorry.

If you do not need the return code, this can be made even simpler:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Notes:

  • Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.

  • Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)

This recipe can be used "naturally" in the shell (as natural as for flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.

Edit:

Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.

Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.

Edit4: Forgot a _ in the first _timeout for the real world GIT example.


Update 2023-08-06: I found a better way to restrict the runtime of git, so the above is just an example.

The following is no more bash-only as it needs setsid. But I found no way to reliably create process group leaders with just bash idioms, sorry.

This recipe is a bit more difficult to use, but very effective, as it not only kills the child, it also kills everything the child places in the same process group.

I now use following:

__git_ps1() { setsid -w /bin/bash -c 'sleep 1 & . /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & wait -n; p=$(/usr/bin/ps --no-headers -opgrp $$) && [ $$ = ${p:-x} ] && /usr/bin/kill -9 0; echo "PGRP mismatch $$ $p" >&2' bash "$@"; }

What it does:

  • setsid -w /bin/bash -c 'SCRIPT' bash "$@" runs SCRIPT in a new process group
  • sleep 1 & sets the timeout
  • . /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & runs the git prompt in parallel
    • /usr/lib/git-core/git-sh-prompt is for Ubuntu 22.04, change it if needed
  • wait -n; waits for either the sleep or __git_ps1 to return
    • The first one wins
  • p=$(/usr/bin/ps --no-headers -opgrp $$) && [ $$ = ${p:-x} ] && is just a safeguard to check setsid worked and we are really a process group leader
    • $$ works here correctly, as we are within single quotes
  • kill -9 0 unconditionally kills the entire process group
    • all git that may still execute
    • including the /bin/bash
  • echo "PGRP mismatch $$ $p" >&2' is never reached
    • This informs you that either setsid is a fake
    • or something else (kill?) did not work as expected

The safeguard protects against the case that setsid does not work as advertised. Without your current shell might get killed, which would make it impossible to spawn an interactive shell.

If you use the recipe and trust setsid, you probably do not need the safeguard, so setsid is the only non-bash-idiom this needs.

Tino
  • 9,583
  • 5
  • 55
  • 60
  • 1
    Bash 4 rocks. That is all. – system PAUSE Mar 05 '15 at 19:28
  • 2
    This actually requires Bash 4.3 or newer. `cc. The 'wait' builtin has a new '-n' option to wait for the next child to change status.` From: http://tiswww.case.edu/php/chet/bash/NEWS – Ben Reser Mar 22 '16 at 06:22
  • 1
    In bash 5.1, wait gets a -p argument which i think could make this even nicer, because you can detect which process has finished first, and handle success and timeout differently. – Tom Anderson Dec 11 '20 at 11:15
18

I prefer "timelimit", which has a package at least in debian.

http://devel.ringlet.net/sysutils/timelimit/

It is a bit nicer than the coreutils "timeout" because it prints something when killing the process, and it also sends SIGKILL after some time by default.

maxy
  • 4,971
  • 1
  • 23
  • 25
  • It does not seem to work pretty well :/ $ time timelimit -T2 sleep 10 real 0m10.003s user 0m0.000s sys 0m0.000s – hithwen Jun 21 '12 at 14:33
  • 3
    Use -t2 not -T2. The big -T is the time from sending SIGTERM until sending SIGKILL. – maxy Jun 27 '12 at 09:26
  • 1
    I would like to add that timelimit 1.8 doesn't seams to works well with fork (`timelimit -t1 ./a_forking_prog` kill only one of the two process), but timeout works. – Jeremy Cochoy Aug 02 '13 at 10:35
  • 1
    If you want timeout to print something when killing the process, just use the "-v" flag. –  Oct 21 '18 at 20:50
9

See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils

pixelbeat
  • 30,615
  • 9
  • 51
  • 60
9

timeout is probably the first approach to try. You may need notification or another command to execute if it times out. After quite a bit of searching and experimenting, I came up with this bash script:

if 
    timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
    timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
    echo 'OK'; #if you want a positive response
else
    echo 'Not OK';
    AND_ALTERNATIVE_COMMANDS
fi
user2099484
  • 4,417
  • 2
  • 21
  • 9
  • 1
    This is an elegant solution. Easy to read, easy to understand. (It needs ` && \\` joining each command, though, if you want to test whether ALL of your commands succeed.) – automorphic Dec 10 '20 at 18:28
8

Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

Actually, I think you can reverse it, meeting your 'bonus' criteria:

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
strager
  • 88,763
  • 26
  • 134
  • 176
5

Simple script with code clarity. Save to /usr/local/bin/run:

#!/bin/bash

# run
# Run command with timeout $1 seconds.

# Timeout seconds
timeout_seconds="$1"
shift

# PID
pid=$$

# Start timeout
(
  sleep "$timeout_seconds"
  echo "Timed out after $timeout_seconds seconds"
  kill -- -$pid &>/dev/null
) &
timeout_pid=$!

# Run
"$@"

# Stop timeout
kill $timeout_pid &>/dev/null

Times out a command that runs too long:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

Ends immediately for a command that completes:

$ run 10 sleep 2
$
4

If you already know the name of the program (let's assume program) to terminate after the timeout (as an example 3 seconds), I can contribute a simple and somewhat dirty alternative solution:

(sleep 3 && killall program) & ./program

This works perfectly if I call benchmark processes with system calls.

loup
  • 8,289
  • 1
  • 12
  • 6
  • 3
    This kills other processes that happen to use the name and doesn't kill the process with the given name if it changes its name (e.g. by writing into `argv[0]`, perhaps with other hacks to make more room). – Jed Apr 16 '12 at 07:48
  • I found a variation of this handy when trying to stop docker containers after a certain amount of time. The docker client didn't seem to accept TERM/INT/KILL in a way that would actually stop the container that was being run in the foreground. So giving the container a name and using `(sleep 3 && docker stop ) &` worked nicely. Thanks! – Mat Schaffer Jul 08 '15 at 02:39
  • yes it's dirt and limited, but can be improved like: `{ sleep 5 && kill -9 $(ps -fe | grep "program" | grep $$ | tr -s " " | cut -d" " -f2); } & SLEEPPID=$!; bash -c "program" && kill -9 $SLEEPPID"` This way, it will kill only jobs in current shell. – ton May 12 '16 at 17:24
2

There's also cratimeout by Martin Cracauer (written in C for Unix and Linux systems).

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
max32
  • 21
  • 1
2

OS X doesn't use bash 4 yet, nor does it have /usr/bin/timeout, so here's a function that works on OS X without home-brew or macports that is similar to /usr/bin/timeout (based on Tino's answer). Parameter validation, help, usage, and support for other signals are an exercise for reader.

# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
    set -m +b
    sleep "$1" &
    SPID=${!}
    ("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
    CPID=${!}
    wait %1
    SLEEPRETVAL=$?
    if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
      RETVAL=124
      # When you need to make sure it dies
      #(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
      wait %2
    else
      wait %2
      RETVAL=$?
    fi
    return $RETVAL
) }
NerdMachine
  • 155
  • 1
  • 3
1

Here is a version that does not rely on spawning a child process - I needed a standalone script which embedded this functionality. It also does a fractional poll interval, so you can poll quicker. timeout would have been preferred - but I'm stuck on an old server

# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
    local timeout=$1; shift
    local interval=$1; shift
    $* &
    local child=$!

    loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
    ((t = loops))
    while ((t > 0)); do
        sleep $interval
        kill -0 $child &>/dev/null || return
        ((t -= 1))
    done

    kill $child &>/dev/null || kill -0 $child &>/dev/null || return
    sleep $interval
    kill -9 $child &>/dev/null
    echo Timed out
}

slow_command()
{
    sleep 2
    echo Completed normally
}

# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command

# or call an external command
wait_on_command 1 0.1 sleep 10
Goblinhack
  • 2,859
  • 1
  • 26
  • 26
  • 1
    Thanks! This is the best solution here, works perfectly on MacOS, does not depends [just] on bash (it runs fine on /bin/sh too) and it is very extremely precise! Thank you a lot for such code! – Prado Sep 13 '20 at 20:48
1

The timeout command itself has a --foreground option. This lets the command interact with the user "when not running timeout directly from a shell prompt".

timeout --foreground the_command its_options

I think the questioner must have been aware of the very obvious solution of the timeout command, but asked for an alternate solution for this reason. timeout did not work for me when I called it using popen, i.e. 'not directly from the shell'. However, let me not assume that this may have been the reason in the questioner's case. Take a look at its man page.

Jayanth
  • 115
  • 1
  • 11
  • 1
    With `--foreground` Ctrl-C (SIGINT) will work, but won't kill any children process. – Pablo Bianchi Jul 05 '22 at 05:24
  • 1
    "I think the questioner must have been aware of the very obvious solution of the timeout command" -- No, when I asked 14 years ago, I was not aware of it. From other answers and comments, you can see that timeout was not available everywhere, at least not by default. But thank you for the mention of the --foreground option. – system PAUSE Mar 16 '23 at 16:48
1

If you want to do it in your script, put this in there:

parent=$$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
nroose
  • 1,689
  • 2
  • 21
  • 28
0
#! /bin/bash
timeout=10
interval=1
delay=3
(
    ((t = timeout)) || :

    while ((t > 0)); do
        echo "$t"
        sleep $interval
        # Check if the process still exists.
        kill -0 $$ 2> /dev/null || exit 0
        ((t -= interval)) || :
    done

    # Be nice, post SIGTERM first.
    { echo SIGTERM to $$ ; kill -s TERM $$ ; sleep $delay ; kill -0 $$ 2> /dev/null && { echo SIGKILL to $$ ; kill -s KILL $$ ; } ; }
) &

exec "$@"
eel ghEEz
  • 1,186
  • 11
  • 24
  • @Tino Sorry I forgot why I changed the process termination line and why I thought this was important to share. Too bad I did not write this down. Perhaps, I found that I needed to pause before checking for a success of kill -s TERM. The year 2008 script from the cook book seems to check for the process's state immediately after sending SIGTERM, possibly leading to an error trying to send SIGKILL to a process that died. – eel ghEEz Mar 03 '15 at 03:57
0

I was presented with a problem to preserve the shell context and allow timeouts, the only problem with it is it will stop script execution on the timeout - but it's fine with the needs I was presented:

#!/usr/bin/env bash

safe_kill()
{
  ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}

my_timeout()
{
  typeset _my_timeout _waiter_pid _return
  _my_timeout=$1
  echo "Timeout($_my_timeout) running: $*"
  shift
  (
    trap "return 0" USR1
    sleep $_my_timeout
    echo "Timeout($_my_timeout) reached for: $*"
    safe_kill $$
  ) &
  _waiter_pid=$!
  "$@" || _return=$?
  safe_kill $_waiter_pid -USR1
  echo "Timeout($_my_timeout) ran: $*"
  return ${_return:-0}
}

my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true  && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd

with the outputs:

Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated

of course I assume there was a dir called scripts

mpapis
  • 52,729
  • 14
  • 121
  • 158
0

My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.

I now use the following :

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

This way the command returns 255 when there was a timeout or the returncode of the command in case of success

Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell

-1

Building on @loup's answer...

If you want to timeout a process and silence the kill job/pid output, run:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

This puts the backgrounded process into a subshell so you don't see the job output.

Cody A. Ray
  • 5,869
  • 1
  • 37
  • 31
-2

A very simplistic way:

# command & sleep 5; pkill -9 -x -f "command"

with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.

nempoBu4
  • 6,521
  • 8
  • 35
  • 40
Francis
  • 415
  • 4
  • 5
  • You realize this is essentially what the OP has in his post and what he indicates he doesn't want, right? Because it always waits the full sleep delay. – Etan Reisner Feb 18 '15 at 06:06
-3

In 99% of the cases the answer is NOT to implement any timeout logic. Timeout logic is in nearly any situation a red warning sign that something else is wrong and should be fixed instead.

Is your process hanging or breaking after n seconds sometimes? Then find out why and fix that instead.

As an aside, to do strager's solution right, you need to use wait "$SPID" instead of fg 1, since in scripts you don't have job control (and trying to turn it on is stupid). Moreover, fg 1 relies on the fact that you didn't start any other jobs previously in the script which is a bad assumption to make.

lhunath
  • 120,288
  • 16
  • 68
  • 77
  • 4
    With access to 100% of the source (and most of the hardware, such as network switches), I would agree that there are probably better solutions than a timeout. But when a 'tlrbsf' is closed-source, binary only, sometimes you have to work around that limitation. – system PAUSE Mar 27 '09 at 15:01
  • @lhunath, "in scripts you don't have job control (and trying to turn it on is stupid)" -- Please clarify here: http://stackoverflow.com/questions/690266/why-cant-i-use-job-control-in-a-bash-script – system PAUSE Mar 27 '09 at 15:42
  • @system PAUSE: Reply http://stackoverflow.com/questions/690266/why-cant-i-use-job-control-in-a-bash-script/690297#690297 is correct, I also commented on it. – lhunath Mar 27 '09 at 17:22
  • 31
    lhunath, what you're saying makes no sense. there are tons of cases where timing out is a good option, e.g. anytime you have to go over the network. – Nate Murray Feb 18 '11 at 21:36
  • An exception: you are writing test scripts to check if an *already running software* doesn't have a timeout problem. – peterh Jul 23 '18 at 08:28
  • One example is `traceroute` running forever in certain cases. Including, as of this writing, `traceroute example.com`. – Beejor May 11 '20 at 20:05
-5

I have a cron job that calls a php script and, some times, it get stuck on php script. This solution was perfect to me.

I use:

scripttimeout -t 60 /script.php
Devstr
  • 4,431
  • 1
  • 18
  • 30