Timing-out-and-moving-on is a programming paradigm generally followed in many multi-process or multi-threaded programs. In a scripting environment like bash, the solution is to do this in a hacky way. Of course coreutils now provides a tool called "timeout" which does exactly what we need - Run the command and timer concurrently. Whenever something exits, the other one gets killed.
Bash is all about finding amazing ways to do things. Here is one of them.
sh -ic "{ sleep $T1; echo one >&3; \
kill 0; } | { sleep $T2; echo two >&3; \
kill 0; }" 3>&1 2>/dev/null
Internals:
Going from inside out.
- In bash
{ some command; }
represents a block of code or inline group ( http://tldp.org/LDP/abs/html/special-chars.html#CODEBLOCKREF ). This construct creates an anonymous function and all the processes invoked come under the same process group. - The structure of the invoked script is
{ } | { }
. - Each inline group has a
kill 0;
. The man page of kill says thatkill 0
signals all processes in the current process group. - When you do
kill 0;
all that happens is the shell will start a new job to execute that kill command, so kill will only kill itself. - All the processes invoked inside
{ }
will be under the same process group, sokill 0
would work there. - Note the output redirection
>&3
within each of the inline groups. Here, we are writing the output to a different file descriptor identified by3
.1
and2
are already defined asSTDOUT
andSTDERR
. - This file descriptor
3
is used to communicate with the outside world. Since there is a pipe between the two inline groups, the output of first group cannot be captured - It goes as input to the second inline group. You have to either write it to a file or a stream represented by a file descriptor. - Just for clarity, I've used
>&3
in both the inline groups. Capturing the output in fd3 outside this setup. sh -ic ""
creates a sub-shell and runs the command that's passed as argument. The-i
flag is to run interactively.- Within this sub-shell, we have our two inline groups sending necessary outputs to fd3. So outside this sub-shell (running interactively ) we can capture fd3 and redirect it however required
3>&1
fd3 is redirected to STDOUT of the session from which the sub-shell is invoked.
Putting everything together: (the short version)
Create a new sub-shell to run interactively. Within this sub-shell, create an inline group that outputs to fd3. Pipe this inline group to another inline group which also outputs to fd3. In the session which invoked the sub-shell, redirect fd3 to STDOUT
.
Why does this work ?
All that we are doing is pipe two inline groups. One of the inline groups contain our main program which shouldn't run longer than $TIMEOUT
. The other inline group would sleep for $TIMEOUT
- both followed by a kill 0;
statement. Whichever exits first is going to call the kill command and all the processes in that process group will be killed. This includes the sub-shell and all the processes within the sub-shell.
Example:
Capture bandwidth usage per process using nethogs for 1 hour.
sh -ic "{ /usr/sbin/nethogs -t >&3; \
kill 0; } | { sleep 3600; \
kill 0; }" 3>&1 2>/dev/null
More about this is discussed here - http://boopathi.in/blog/capturing-per-process-bandwidth-usage-using-nethogs.