Last active
August 29, 2015 14:12
-
-
Save RaminHAL9001/f551c97634d74cc91257 to your computer and use it in GitHub Desktop.
Command line oven timer program written in Bash script. Specify wait time in "hh:mm:ss" or "mm:ss" format.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# An oven timer. This script uses the "notify-send" program to display a | |
# message after a certain amount of time specified as command line arguments. | |
# It optionally plays a sound using "mplayer" after calling "notify-send". | |
# Waiting time can be specified in a user friendly (hours : minutes : seconds) | |
# format, e.g. "hh:mm:ss" or "mm:ss". | |
# | |
# Copyright (C) Ramin Honary 2014, all rights reserved. | |
# Licensed under the GNU General Public License: | |
# http://www.gnu.org/licenses/gpl.html | |
#################################################################################################### | |
# Example usage: | |
# timer -v 0:30 'Hello, world!' | |
# will output the amount of time remaining every second, wait for 30 seconds, | |
# then use "notify-send" to display a "Hello, world!" notification. "-v" | |
# indicates verbose behavior, which reports how much time is remaining every | |
# second. | |
# timer 2:45:00 | |
# will wait for 2 hours and 45 minutes and call "notify-send" using the default | |
# message. Since "-v" is not specified, it will not report how much time is | |
# remaining, | |
#################################################################################################### | |
# First override these variables to whatever defaults you would like. | |
MESSAGE='Time is up!' | |
# ^ MESSAGE is the default message to use with the "notify-send" program. | |
SOUND_FILE='/usr/share/sounds/gnome/default/alerts/sonar.ogg'; | |
ICON_FILE='/usr/share/icons/ubuntu-mobile/status/scalable/alarm-clock.svg'; | |
# ^ SOUND_FILE and ICON_FILE are optional, so you can comment out these lines of | |
# code entirely if you don't want to use them. | |
#################################################################################################### | |
require() { | |
# This function will check for the existence of programs in /bin and | |
# /usr/bin that are necessary to make the script work. Reports an error and | |
# evaluates "exit 1" if the file path argument is not found or executable. | |
if [ -x "$*" ]; | |
then | |
echo "$*"; | |
else | |
echo "Script requires \"$*\" program, does not appear to be installed." >&2; | |
exit 1; | |
fi; | |
} | |
# Check for the following essential programs. The "notify-send" program is | |
# included in most modern desktop Linux distributions, it will simply pop-up a | |
# message on the screen in front of all windows. | |
NOTIFY_SEND="$( require '/usr/bin/notify-send' )" || exit 1; | |
DATE="$( require '/bin/date' )" || exit 1; | |
SED="$( require '/bin/sed' )" || exit 1; | |
GREP="$( require '/bin/grep' )" || exit 1; | |
SLEEP="$( require '/bin/sleep' )" || exit 1; | |
DC="$( require '/usr/bin/dc' )" || exit 1; | |
# STEP 1: parse the command line arguments passed to this script. | |
# Verbose indicates whether or not this program will print out how much time is | |
# remaining in the timer every second. | |
VERBOSE=false; | |
# Loop through the command line arguments, checking for optional flags. In | |
# "bash", the "$1" variable is always the next command line argument. | |
while [ -n "$1" ]; | |
do | |
case "$1" in | |
('--sound-file=') SOUND_FILE="${1%%'--sound-file='}"; shift;; | |
('--icon-file=') ICON_FILE="${1%%'--icon-file='}"; shift;; | |
('-S'|'--no-sound') SOUND_FILE=''; shift;; | |
('-v'|'--verbose') VERBOSE=true; shift;; | |
(*) break;; | |
esac; | |
done; | |
# Now that we have the optional arguments we need at least a wait time argument | |
# specified on the command line. Retrieve that now. | |
TIME="$1"; shift; | |
if [ -z "${TIME}" ]; | |
then | |
echo 'Please specify time to wait.' >&2; | |
exit 1; | |
fi; | |
# Dump the rest of the command line arguments into the "MESSAGE" variable, if | |
# there are any left. If there are no arguments after the TIME argument, the | |
# default message is used. | |
if [ -n "$*" ]; | |
then | |
SUBMSG="${MESSAGE}"; | |
MESSAGE="$*"; | |
fi; | |
# Command line argument parsing is now completed. | |
# STEP 2: check if the file resources exist. This is done after command line | |
# arguments are parsed because the file paths of these resources may have been | |
# changed by the command line parameters. | |
resource() { | |
# This function checks if file resources exist, specifically the sound file | |
# and the icon file. If the file does not exist, an error message is | |
# printed and "exit 1" is called. If the argument passed is a null string, | |
# no error occurs. | |
local RSRC="$1"; shift; | |
local ERRMSG="$*"; | |
if [ -n "${RSRC}" -a ! -r "${RSRC}" ]; | |
then | |
echo "Could not find ${ERRMSG}: ${RSRC}" >&2; | |
exit 1; | |
fi; | |
} | |
# Check if the "ICON_FILE" and "SOUND_FILE" resources exist. If they are not | |
# specified, no error occurs. | |
resource "${ICON_FILE}" 'icon file'; | |
resource "${SOUND_FILE}" 'sound file;' | |
# "mplayer" is used to play the sound. If a sound file is not specified, this | |
# check need not be performed. If a sound file is specified and "mplayer" is | |
# not installed on this system, a warning message is printed to the standard | |
# error stream, and execution of this program continues. | |
if [ -n "$SOUND_FILE" ]; | |
then | |
MPLAYER=$(require '/usr/bin/mplayer'); | |
fi; | |
# STEP 3: Parse the input $TIME that was specified as on the command line. | |
declare -gi SECONDS='-1'; | |
# SECONDS is declared to be an integer, and is initialized to negative 1. If | |
# seconds is not set to zero or a positive integer, that indicates an error | |
# occurred parsing the input time argument. | |
parse_time() { | |
# Parse time expressions using "sed", convert times like "1:23:45" (hours : | |
# minutes : seconds) or "1:23" (minutes : seconds) to an expression that | |
# can be computed by the "dc" program. | |
local REGEX="$1"; shift; | |
local EQUATION="$1"; shift; | |
EQUATION=$(echo "$*" | "${SED}" -ne "s,${REGEX},${EQUATION},p") | |
if [ -z "${EQUATION}" ]; | |
then | |
# This should never happen, because the "parse_time()" function is only | |
# ever called by other functions in this program. | |
echo 'Invalid time computation.' >&2; | |
exit 1; | |
fi; | |
declare -gi SECONDS=$("${DC}" -e "${EQUATION}") || exit 1; | |
# Global variable SECONDS is changed here. | |
} | |
D='\([[:digit:]][[:digit:]]\?\)'; | |
# "D" is a regular expression that matches one or two digit characters. I will | |
# be using it a lot, so I saved it to a variable. | |
days_hrs_mins_secs() { | |
parse_time '\([[:digit:]]\+\)'":$D:$D:$D" '86400 \1 * 3600 \2 * 60 \3 * \4 + + + p' "$@"; | |
} | |
hrs_mins_secs() { parse_time "$D:$D:$D" '3600 \1 * 60 \2 * \3 + + p' "$@"; } | |
mins_secs() { parse_time "$D:$D" '60 \1 * \2 + p' "$@"; } | |
secs() { | |
# Use Grep to make sure the input contains ONLY digits. | |
if echo "$*" | "${GREP}" '[^[:digit:]]' >'/dev/null' 2>&1; | |
then | |
declare -gi SECONDS='-1'; | |
else | |
declare -gi SECONDS=$(printf '%i' "$*"); | |
fi; | |
} | |
case "${TIME}" in | |
(*:??:??:??) days_hrs_mins_secs "${TIME}";; | |
(??:??:??) hrs_mins_secs "${TIME}";; | |
(?:??:??) hrs_mins_secs "${TIME}";; | |
(??:??) mins_secs "${TIME}";; | |
(?:??) mins_secs "${TIME}";; | |
(?:?) mins_secs "${TIME}";; | |
(*) secs "${TIME}";; | |
esac; | |
if [ -z "${SECONDS}" -o "${SECONDS}" -lt 0 ]; | |
then | |
echo "Invalid input time \"$TIME\"" >&2; | |
echo 'Time should be written as a number of seconds, or as one of the following formats:'; | |
echo ' mm:ss'; | |
echo ' hh:mm:ss'; | |
echo ' D:hh:mm:ss'; | |
exit 1; | |
fi; | |
# Parsing of $TIME parameter is now complete. | |
display_time() { | |
# This function takes a number of seconds as an argument and uses Bash's | |
# built-in arithmetic function via the "let" expression to convert the | |
# seconds to a time string of the format: (days : hours : minutes : | |
# seconds), where "days" and "hours" are not printed if they are zero. | |
let DAY="$1"; shift; | |
let SEC="${DAY} % 60"; | |
let DAY="${DAY} / 60"; | |
let MIN="${DAY} % 60"; | |
let DAY="${DAY} / 60"; | |
let HRS="${DAY} % 24"; | |
let DAY="${DAY} / 24"; | |
if [ "${DAY}" -ne 0 ]; | |
then | |
printf '%i:%.2i:%.2i:%.2i' "${DAY}" "${HRS}" "${MIN}" "${SEC}"; | |
elif [ "${HRS}" -ne 0 ]; | |
then | |
printf '%.2i:%.2i:%.2i' "${HRS}" "${MIN}" "${SEC}"; | |
else | |
printf '%.2i:%.2i' "${MIN}" "${SEC}"; | |
fi; | |
} | |
### MAIN PROGRAM ### | |
# The start time is taken right now using the "date" program formatted as a | |
# single integer value indicating the number of seconds since the UNIX epoch. | |
# The end time is the start time plus the number of seconds specified as the | |
# command line argument to this program. | |
NOW="$( "${DATE}" )"; | |
let START_TIME=$("${DATE}" -d "${NOW}" '+%s'); | |
let END_TIME="${START_TIME} + ${SECONDS}"; | |
echo "Set timer for ${SECONDS} seconds on ${NOW}" | |
# Then a "while" loop is entered which sleeps for one second at the start of | |
# each loop. Every second, the "date" program is used to retrieve the current | |
# UNIX epoch time. The Bash arithmetic "let "expression is used to compute the | |
# difference between the current time and the end time. If this time difference | |
# is less than or equal to zero seconds, the alarm is triggered and the loop is | |
# exited by evaluating "exit 0". | |
# | |
# If the "-v" or "--verbose" command line arguments have been specified, then | |
# every loop also reports the number of seconds remaining until the timer is | |
# triggered. | |
while "${SLEEP}" 1; | |
do | |
let NOW="$("${DATE}" '+%s')"; | |
let TIME="${END_TIME} - ${NOW}"; | |
if [ "${TIME}" -le 0 ]; | |
then | |
# The notify-send program is executed in an "if" expression because I | |
# do not want the sound to play if there has been no notification. | |
if "${NOTIFY_SEND}" -i "${ICON_FILE}" "${MESSAGE}" "${SUBMSG}"; | |
then | |
# Use of mplayer is optional, and predicated on whether or not it | |
# is installed and whether or not a sound file has been specified. | |
if [ -x "${MPLAYER}" -a -r "${SOUND_FILE}" ]; | |
then | |
"${MPLAYER}" --quiet "${SOUND_FILE}" >'/dev/null' 2>&1; | |
fi; | |
fi; | |
exit 0; | |
elif "${VERBOSE}"; | |
then | |
DISPLAY_TIME="$(display_time "${TIME}")"; | |
if [ -n "${SUBMSG}" ]; | |
then | |
echo "${DISPLAY_TIME} until ${MESSAGE}"; | |
else | |
echo "${DISPLAY_TIME}"; | |
fi; | |
fi; | |
done; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment