On some systems, certain commands like df
and prtdiag
(Sun only) can hang indefinitely due to various causes. I'm trying to harden my ksh
scripts so that should they encounter a hang on these, they would fail rather than stall indefinitely. To this end, I'm planning on using the timeout
command, but it isn't universally available on 100% of our footprint, so I must conditionally use timeout
only if it exists, and the bare command if it doesn't. I would like to find a compact one-liner for this as I'll need to plug this into numerous existing command chains with minimal increase of complexity.
For most commands, this is simple. But timeout
returns a non-zero status code when it times out (124, 143 or 271), not just when it fails to run because it doesn't exist (1 or 127). Therefore, the simple ||
operator cannot differentiate between "no such command" and "the operation timed out". As a result, this won't work:
(timeout 5 df || df) # will execute bare df if there is no timeout *or* if the first df times out, thus hanging
Another option that tests for the timeout command without trying execute it also doesn't work for the same reason. The timeout itself on a time-out condition will look to ||
the same as if the timeout command doesn't exist:
(command -v timeout >/dev/null && timeout 5 df || df) # same result
I need a compact if-then-else for return codes where the return code of the last piece (else) is driven solely by the first piece (if) and not influenced by the middle one (then). If the timeout
command exists, use it, otherwise, execute the bare command. I do not want a time-out condition to result in executing the bare command.
Here's one way that works correctly, by testing twice:
(command -v timeout >/dev/null || df; command -v timeout >/dev/null && timeout 5 df)
But this doesn't feel very elegant; I'm having to repeat the command -v timeout >/dev/null
twice. Another option that works with only one test:
(command -v timeout >/dev/null; if [ $? -eq 0 ]; then timeout 5 df; else df; fi)
But the explicit if-then-else-fi with the semi-colons and such feels heavy-handed and not as compact as it should be.
Any suggestions for a cleaner, more compact way of doing this? It would need to work in as old as ksh88
on AIX, Sun, HP-UX and Linux.
Note that A && B || C
executes C if A or B fails. You need to use if
to separate B and C properly.
Sounds like you'll want to create a function, and likely in a library file: if this is in file my_timeout.ksh
timeout() {
if command -v timeout >/dev/null; then
command timeout "$@"
# possibly capture $? and add a case statement
# if you need to do special things upon actual timeout
else
"$@"
fi
}
Then in your scripts the minimal change is:
. my_timeout.ksh
timeout df --with args
The ksh .
will look in the PATH for files to source (at least in ksh93, not 100% sure about ksh88). Otherwise, source it by absolute path.