My approach would be this one:
Execute command as background process 1
Execute "watchdog timer" as background process 2
Set up a handler to trap a termination signal in the parent shell
Wait for both processes to complete. The process that terminates first, sends the termination signal to the parent.
The parent's trap handler kills both background processes via job control (one of them has already terminated by definition, but that kill will be a harmless no-op because we are not using PIDs, see below)
I tried to circumvent the possible race condition addressed in the comments by using the shell's job control IDs (which would be unambiguous within this shell instance) to identify the background processes to kill, instead of system PIDs.
#!/bin/sh
TIMEOUT=$1
COMMAND='sleep 5'
function cleanup {
echo "SIGTERM trap"
kill %1 %2
}
trap cleanup SIGTERM
($COMMAND; echo "Command completed"; kill $$) &
(sleep $TIMEOUT; echo "Timeout expired"; kill $$) &
wait
echo "End of execution"
Result for TIMEOUT=10
(command terminates before watchdog):
$ ./timeout.sh 10
Command completed
SIGTERM trap
End of execution
Result for TIMEOUT=1
(watchdog terminates before command):
$ ./timeout.sh 1
Timeout expired
SIGTERM trap
End of execution
Result for TIMEOUT=5
(watchdog and command terminate "almost" simultaneously):
./tst.sh 5
Timeout expired
Command completed
SIGTERM trap
End of execution
command & pid=$! ; sleep 5 && kill $pid
command
finishes rapidly there is a slim chance of thepid
being reused by another process starting up before thekill
command runs? I wouldn't want that in production code....timeout
program and use it?