What is a simple way to let a command run for 5 minutes? [duplicate]

Is there an easy way to let a specific command (only terminable through Ctrl-C) run for 5 minutes automatically?

For example:

minute-command 5-minutes ping www.google.com

or any other command that does not terminate itself.

I'd like to be able to specify a time limit, not only 5 minutes.


There are (at least) two programs that provide this functionality:

NAME

timelimit — effectively limit the absolute execution time of a process

SYNOPSIS

timelimit [-pq] [-S killsig] [-s warnsig] [-T killtime] [-t warntime] command [arguments ...]

and

NAME

timeout - run a command with a time limit

SYNOPSIS

timeout [OPTION] DURATION COMMAND [ARG]...
timeout [OPTION]

They are packaged as follows:

$ dlocate `which timeout timelimit`
timelimit: /usr/bin/timelimit
coreutils: /usr/bin/timeout

Comparison:

/-----------------------------+------------+----------------\
|            Feature          |  timelimit |     timeout    |
+=============================+============+================+
|   time to run               | -t time    | first argument |
+-----------------------------+------------+----------------+
|   terminate signal          | -s signal  | -s signal      |
+-----------------------------+------------+----------------+
|   grace period              | -T time    | -k time        |
+-----------------------------+------------+----------------+
|   kill signal               | -S signal  | (note 1)       |
+-----------------------------+------------+----------------+
|   propagate signals         | -p         | (note 2)       |
\-----------------------------+------------+----------------/

Notes:

  1. timeout always uses SIGKILL as its last-resort signal.
  2. timeout does not have any functionality to exit with a signal when the child program does so.

The exit status of the two programs differ, but that's hard to summarise neatly, so I suggest you consult the manual pages yourself for that.

As timeout is installed on more systems by default (coreutils is a standard package in many distributions), I suggest you use that unless you need the extra functionality provided by timelimit.


Actually, this is what timeout is for:

TIMEOUT(1)                          User Commands                         TIMEOUT(1)

NAME
       timeout - run a command with a time limit

SYNOPSIS
       timeout [OPTION] DURATION COMMAND [ARG]...
       timeout [OPTION]

DESCRIPTION
       Start COMMAND, and kill it if still running after DURATION.

lx@lxtp:~$ dpkg -S /usr/bin/timeout
coreutils: /usr/bin/timeout

Pure bash built in, without coreutils

I found that this solution works in bash relying on a built-in command without calling an external executable. It works on system where eventually are not even been installed the coreutils [1]

YourCommand & read -t 300 ;  kill $!                           # 1st version
YourCommand & read -t 300 || kill $!                           # 2nd version 

Explanations: as usual when you send a command in the background with &, its PID is stored into the internal variable $! (present in the modern version of dash, csh, bash, tcsh, zsh...).
What really makes the difference among the shells is the presence of the built-in command read[2] and of the option -t. In the 1st version if the user will not complete a line of input before the specified amount of seconds the instruction will be terminated and an error return code will be generated.

-t TIMEOUT Cause read to time out and return failure if a complete line of input is not read within TIMEOUT seconds.

The second version works as the 1st but you can abort the killing timeout just pressing enter.
Indeed the or operator || executes the kill statement only if the read command exits with a return code different from zero, as when the timeout is expired. If you press enter before that moment, it will return 0 and it will not kill your previous command.


Coreutils solutions [1]

When coreutils are present on your system and you have no need to save the time and the resources to call an external program, timeout and sleep and are both perfect ways to reach your goal.

timeout The use of timeout is straightforward.
Eventually you can consider to use also the -k option to send an additional kill signal if the first fails.

timeout 5m YourCommand                                         # 3rd version 

sleep With sleep you can use your fantasy or take some inspirations[3]. Note that you can leave your command in background or in foreground (e.g. top usually needs to be in foreground).

YourCommand & sleep 5m; kill $!                                # 4th Background
YourCommand & pid=$! ; (sleep 5m; kill $pid;) &                # 5th Background

bash -c '(sleep 5m; kill $$) & exec YourCommand'               # 6th Foreground
(cmdpid=$BASHPID; (sleep 5m; kill $cmdpid) & exec YourCommand) # 7th Foreground

Explanations

  • In the 4th version you execute in background YourCommand then your shell sleeps for 5 minuites. When it will be finished the last background process ($!) will be killed. You stop your shell.
  • In the 5th version instead you execute in background YourCommand and you store immediately that PID in the variable $pid. Then you execute in background a nap of 5 minutes and its consequent command that will kill that stored PID. Since you sent this group of commands in background you do not stop your shell. You need to store the PID in a variable because the value of $! can be updated by an eventual execution of another program in background. In simple words you avoid the risk to kill the wrong process or no process at all.
  • In the 6th version it is called a new bash shell that will suicide itself in 5 minutes via $$, then it is executed your command that remains in foreground.
  • In the 7th version it is invoked a subshell () that stores its PID in a variable (cmdpid) and kills itself with another subshell sent in background execution, then run YourCommand in foreground.

Of course in each version you can send the kill signal you need, from the default one to the extreme kill -9, to be used only when really needed.

References

  • [1] The Coreutils
  • [2] The Bash Beginners Guide
  • [3] The BashFAQ

For your particular case, most implementations of ping support -c or --count to terminate after a particular number of pings:

ping -c 300 host.example.com

For a more general solution, see the other answers.


You can do it with a simple script like this:

#!/bin/bash

#
# $1 is the time to let the program run, and $2, $3, ... are the command itself.
#

"${@:2}" &
PID=$!
sleep "${@:1:1}"
kill -2 $PID

(signal SIGINT=2 used as per Matija Nalis' suggestion in the comment below).

An explanation of the (uncommon?) bash expression $@:...: the positional parameters, ($*, $@, "$*", "$@") admit the following extra specification, for instance:

"${@:START:COUNT}"

which means: of all parameters, take COUNT of them, the first one to be taken being that at the STARTth position; if COUNT is omitted, take all of them till the end, beginning with the STARTth position. Remember that $0 is the program name. If START is negative, start counting from the end, and remember that COUNT cannot be negative, hence the last argument is "${@:-1}". Also, just about always include the positional parameters inside double quotes.