#!/bin/sh usage() { printf "Usage: %s [-l DEBUG_LEVEL] [-v] [-h] [-t TIMEOUT] [-c COMMAND]\n\n" "$0" } help() { cat << EOH SYNOPSIS $0 [-l DEBUG_LEVEL] [-v] [-h] [-t TIMEOUT] [-c COMMAND] DESCRIPTION \`Timeout\` executes a command until the timeout is over. When the command is finished before the timeout then the exit code is \`0\` else it's \`1\`. OPTIONS -h Show this help -l debug|info|notice|warning|error Debug level -v Display the command output (stderr) -c COMMAND The command to execute \`COMMAND=... $0\` works to. -t TIMEOUT The timeout in seconds (default: 10) \`TIMEOUT=... $0\` works to. EOH } on_interrupt() { log -l notice "Process aborted!" exit 130 } create_script() { printf "#!/bin/sh\n%s\n" "$1" > "$2" chmod +x "$SCRIPT_FILE" log -t -l debug "$SCRIPT_FILE created" } run_script() { if [ "$2" -eq 0 ]; then "$SCRIPT_FILE" >/dev/null 2>&1 & else "$SCRIPT_FILE" 2>"$LOG_FILE" >&2 & fi log -t -l debug "$SCRIPT_FILE started" printf "%d" $! } stop_delete_script() { ( kill -9 "$1" 2>/dev/null \ && log -t -l debug "Process \"$1\" killed" ) || log -t -l debug "Process \"$1\" does not exist" test -f "$2" && rm -f "$2" test -f "$3" && rm -f "$3" } check_pid() { PID=$1 LOOP=$2 for _ in $(seq 1 "$((LOOP*2))"); do test -d "/proc/$PID/" && sleep 0.5 || STATUS=0 done printf "%d" ${STATUS:-1} } main() { while getopts "l:ht:c:v" option; do case "${option}" in h) help; exit 0;; t) TIMEOUT="$OPTARG";; c) COMMAND="$OPTARG";; l) LOG_VERBOSE="$OPTARG";; v) VERBOSE=1;; ?) log -l error "$(usage)"; exit 1;; esac done TIMEOUT="${TIMEOUT:-10}" VERBOSE="${VERBOSE:-0}" if [ -z "$COMMAND" ]; then log -l error "Command is required!" exit 1 fi SCRIPT_FILE="$(mktemp)" LOG_FILE="$(mktemp)" create_script "$COMMAND" "$SCRIPT_FILE" SCRIPT_PID=$(run_script "$SCRIPT_FILE" "$VERBOSE") ( test -f "$LOG_FILE" && tail -f "$LOG_FILE" | while read -r OUTPUT; do LOG_VERBOSE=info log -t -l info "$OUTPUT" done )& EXIT_STATUS=$(check_pid "$SCRIPT_PID" "$TIMEOUT") stop_delete_script "$SCRIPT_PID" "$SCRIPT_FILE" "$LOG_FILE" log -t -l debug "Exit code is $EXIT_STATUS" exit "$EXIT_STATUS" } log() { LOG_VERBOSE="${LOG_VERBOSE:-info}" LEVEL=info TIME= while getopts "tl:" option; do case "${option}" in l) LEVEL="$OPTARG"; shift $((OPTIND-1));; t) TIME="$(printf "[%s] " "$(date +'%Y-%m-%dT%H:%M:%S.%s')")"; shift $((OPTIND-1));; *) exit 1;; esac done if [ -t 2 ] && [ -z "${NO_COLOR-}" ]; then case "${LEVEL}" in debug) COLOR="$(tput setaf 3)";; notice) COLOR="$(tput setaf 4)";; warning) COLOR="$(tput setaf 5)";; error) COLOR="$(tput setaf 1)";; *) COLOR="$(tput sgr0)";; esac fi case "${LEVEL}" in debug) LEVEL=100;; notice) LEVEL=250;; warning) LEVEL=300;; error) LEVEL=400;; *) LEVEL=200;; esac case "${LOG_VERBOSE}" in debug) LOG_VERBOSE_VALUE=100;; notice) LOG_VERBOSE_VALUE=250;; warning) LOG_VERBOSE_VALUE=300;; error) LOG_VERBOSE_VALUE=400;; *) LOG_VERBOSE_VALUE=200;; esac if [ $LEVEL -ge $LOG_VERBOSE_VALUE ]; then printf "%s\n" "$*" | while IFS='' read -r LINE; do printf "%s%s%s\n" "${COLOR:-}" "${TIME:-}" "$LINE" >&2 done fi } trap on_interrupt INT main "$@"