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 processSYNOPSIS
timelimit [-pq] [-S killsig] [-s warnsig] [-T killtime] [-t warntime] command [arguments ...]
and
NAME
timeout
- run a command with a time limitSYNOPSIS
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:
-
timeout
always usesSIGKILL
as its last-resort signal. -
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 shellsleep
s 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.