Skip to content

Instantly share code, notes, and snippets.

@elundmark
Last active August 12, 2022 05:25
Show Gist options
  • Save elundmark/59dcb9ada7263ee3ff68 to your computer and use it in GitHub Desktop.
Save elundmark/59dcb9ada7263ee3ff68 to your computer and use it in GitHub Desktop.
Verbose Sleep - echo the progress to stderr
#!/usr/bin/env bash
get_ms () {
date +'%s%N' | grep -oP '^[0-9]{13}'
}
show_usage () {
# *******************************************************************
read -d '' help_text <<- EOF
vsleep - verbose sleep
Mimic sleep but also output the status and progress to stderr.
This scripts license:
Date: 2015-12-07 12:30 Author: Erik Lundmark <e@3r1k.se>
License: MIT <http://opensource.org/licenses/MIT>
License for sleep:
Copyright © 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
<http://gnu.org/licenses/gpl.html>
WARNING:
Just like sleep you should not expect an exact time
to elapse, and this issue is even more prominent
using this script; sleeping for 1 hour with a '-i' interval
set to 1 will almost add a minute to the actual time elapsed.
Increase the interval '-i' to a larger number than the
default 5 to lessen the effects of this.
USAGE:
/path/to/vsleep OPTION NUMBER[SUFFIX]
OPTIONS:
-v
Show more output (seconds and seconds elapsed).
-i NUMBER
Interval between reports to stderr.
The default interval is 5 seconds - see WARNING
If the NUMBER[SUFFIX] argument(s) is smaller
than this number it will be counted as a
"short" sleep and will only output the status
on start and exit.
EOF
# *******************************************************************
echo "$help_text" 1>&2
}
# $ time sleep 1s 2s 3s 4s
# > real 0m10.005s
# $ time /path/to/vsleep 0.4m 0.4m 0.2m 2>/dev/null
# > real 1m0.157s
status_line () {
local w="$1"
local I="$2"
local s="$3"
local v="$4"
local e=""
if [[ $w -ge 0 ]] ; then
if [[ $w -eq 0 ]] ; then
e=" 0% - sleeping"
else
e="$(echo "scale=3; ( $(( w * I )) / $s ) * 100" | bc | grep -oP '^[0-9]+')"
[[ ! $e =~ ^[0-9] ]] && e=0
e="$(printf "%5s" "$e""%") - sleeping"
fi
[[ $v -eq 1 ]] && e="$e"": $s seconds [$(( w * I ))/$s]"
echo -ne "\r""$e" 1>&2
else
# final echo (paste over entire line)
e=" 100% - finished"
[[ $v -eq 1 ]] && e="$e"": $s seconds [$s/$s]"
echo -e "\r""$e" 1>&2
fi
}
declare -i start_ms=0
declare -i delayed_ms=0
declare -i verbose=0
declare -i i=0
declare -i C_EC=1
declare -i E_EC=0
declare -i while_num=0
declare -i interval=5
declare arglen
declare args
declare sleep_arg
declare sec_sleep
declare re
if [[ $# -eq 0 ]] ; then
# let it fail by itself
sleep
exit $?
fi
while getopts ":vi:" options ; do
case "$options" in
v) verbose=1;;
i) if [[ $OPTARG =~ ^[0-9]+$ && $OPTARG -gt 0 ]] ; then
interval="$OPTARG"
else
echo "-i takes a positiv integer" 1>&2
exit 1
fi
;;
h) show_usage
exit 0;;
\?) show_usage
exit 0;;
*) show_usage
exit 1;;
esac
done
args=( "${@:OPTIND}" )
arglen=${#args[@]}
for (( i = 0; i < arglen; i++ )) ; do
start_ms=$(get_ms)
while_num=0
sleep_arg="${args[i]}"
# read the manual for sleep for the valid arguments
# remember to allow any floating numbers
re='^([0-9.]+)([smhd]?)$'
if [[ $sleep_arg =~ $re && $(wc -l <<< "$(grep -oP '\.' <<< "$sleep_arg")") -le 1 ]] ; then
# '1.0s' --> BASH_REMATCH=('1.0s' 1.0' 's')
# convert the argument to whole seconds
case "${BASH_REMATCH[2]}" in
's') sec_sleep="${BASH_REMATCH[1]}";;
'm') sec_sleep="$(echo "scale=0; ${BASH_REMATCH[1]} * 60" | bc)";;
'h') sec_sleep="$(echo "scale=0; ${BASH_REMATCH[1]} * 60 * 60" | bc)";;
'd') sec_sleep="$(echo "scale=0; ${BASH_REMATCH[1]} * 60 * 60 * 24" | bc)";;
*) sec_sleep="${BASH_REMATCH[1]}";;
esac
# capture new match and check that it's larger than $interval
if [[ $sec_sleep =~ ^([0-9]+) && ${BASH_REMATCH[1]} -gt $interval ]] ; then
sec_sleep="${BASH_REMATCH[1]}"
# divide by 10 to get the number of (manual sleep 10s) loops
# sleep_div should always be 1 or greater
sleep_div="$(echo "scale=0; $sec_sleep / $interval" | bc)"
status_line "$while_num" "$interval" "$sec_sleep" "$verbose"
while [[ $while_num -lt $sleep_div ]] ; do
# exit status should be the EC of sleep
sleep "$interval""s"; C_EC=$?
let while_num++
status_line "$while_num" "$interval" "$sec_sleep" "$verbose"
done
if [[ $interval -gt 1 ]] ; then
# sleep for the % remainder
sleep "$(echo "scale=0; $sec_sleep % $interval" | bc)"s; C_EC=$?
fi
status_line '-1' "$interval" "$sec_sleep" "$verbose"
else
status_line 0 "$interval" "$sec_sleep" "$verbose"
sleep "$sleep_arg"; C_EC=$?
status_line '-1' "$interval" "$sec_sleep" "$verbose"
fi
else
# let it fail (or succeed) naturally
echo "" 1>&2
echo "$(basename "$0"): not a valid argument" 1>&2
sleep "$sleep_arg"; C_EC=$?
fi
if [[ $E_EC -eq 0 && $C_EC -ne 0 ]] ; then
E_EC="$C_EC"
fi
delayed_ms=$(echo "scale=0; $delayed_ms + ( ( $(get_ms) - $start_ms ) - ( $sec_sleep * 1000 ) )" | bc | grep -oP '^[0-9]+')
done
if [[ $E_EC -eq 0 && $C_EC -eq 0 && $verbose -eq 1 ]] ; then
echo " ${args[*]} +~ $delayed_ms ms delay" 1>&2
fi
exit "$E_EC"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment