Skip to content

Instantly share code, notes, and snippets.

@stephencroberts
Last active May 4, 2019 01:03
Show Gist options
  • Save stephencroberts/a96d63d1e297312c4f001d2388a1a10c to your computer and use it in GitHub Desktop.
Save stephencroberts/a96d63d1e297312c4f001d2388a1a10c to your computer and use it in GitHub Desktop.
Shell script to time a command with a timeout (POSIX compliant)
#!/bin/sh
#
# Time a command with a timeout
#
# Author: Stephen Roberts <[email protected]>
# Version: 1
#
# Usage: twto [OPTIONS] TIMEOUT COMMAND
#
# Disable globbing
set -f
usage() {
printf "\nUsage: twto [OPTIONS] TIMEOUT COMMAND\n
Time a command with a timeout\n
Options:
-s, --timeout-success Exit 0 on timeout\n
Example: twto 60 ls\n"
}
err() {
printf "\e[31m[ERROR]\t%s\e[0m\n" "$2" >&2
exit "$1"
}
EXIT_NO_TIME=65
EXIT_NO_FP=66
EXIT_TIMED_OUT=67
# Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
-s|--timeout-success)
EXIT_TIMED_OUT=0
shift
;;
*)
POSITIONAL="$POSITIONAL $1"
shift
;;
esac
done
# shellcheck disable=SC2086
set -- $POSITIONAL
TIMEOUT="$1"
shift
COMMAND="$*"
[ -z "$TIMEOUT" ] && usage && exit 0
[ -z "$COMMAND" ] && usage && exit 0
command -v time >/dev/null || err "$EXIT_NO_TIME" "Command not found: time"
# Create a named pipe
output=$(mktemp -u)
mkfifo -m 600 "$output"
# Time the command
( ( time $COMMAND>/dev/null ) 2>"$output"; echo "done" >"$output" ) &
time_pid=$!
# Start timeout process
( sleep "$TIMEOUT" && echo timeout >"$output" ) &
sleep_pid=$!
# Trap exit to clean up
trap 'kill -0 "$time_pid" 1>/dev/null 2>&1 '\
' && kill "$time_pid" '\
' && wait "$time_pid" 2>/dev/null;'\
'kill -0 "$sleep_pid" 1>/dev/null 2>&1 '\
' && kill "$sleep_pid" '\
' && wait "$sleep_pid" 2>/dev/null;'\
'rm -f "$output"' EXIT
# Read from pipe until we see either "timeout" or "done"
while read -r line; do
if [ "$line" = "done" ]; then
exit 0
elif [ "$line" = "timeout" ]; then
echo "$TIMEOUT"
err "$EXIT_TIMED_OUT" "timed out"
elif test "${line#*real}" != "$line"; then
# Parse time command output
mins=${line%m*}
mins=${mins#real}
secs=${line#*m}
secs=${secs%s}
# Attempt floating point arithmetic
if command -v bc >/dev/null; then
total=$(echo "$mins * 60 + $secs" | bc | xargs -I {} printf '%.3f' {})
elif command -v perl >/dev/null; then
total=$(perl -E "say $mins * 60 + $secs")
else
err "$EXIT_NO_FP" \
"No command to handle floating point arithmetic found! Tried bd, perl."
fi
echo "$total"
fi
done <"$output"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment