1

I built a shell script that sleeps for a specified amount of minutes and shows notification when it is done.

TIME=$(zenity --scale --title="Next Session in (?) minutes")

sleep $TIME'm'

BEEP="/usr/share/sounds/freedesktop/stereo/complete.oga"
paplay $BEEP
notify-send "Next Session" "Press <Ctrl><Shift><s> to run the script again"

I prevented multiple instance of the program from executing using a file based approach at the beginning of the code. When a user wants to run the script while another instance is running, it shows a notification that the script is already running.

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    notify-send "Already Running" $SECONDS
    exit
fi

trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

and finally remove the temporary file at the end of the script

rm -f ${LOCKFILE}

Now I want to add a text to the notification that tells how many seconds are left for the sleep command in my shell script to end. (changing the already running notification as follows)

notify-send "Already Running" $SECONDS

To implement the sleep command with my own controlled while loop would affect the overall performance of the computer. I think the sleep command is a better option as it optimizes the process by sending itself to a waiting state in the process queue. Is there any way I can go around the problem?

Merhawi Fissehaye
  • 2,482
  • 2
  • 25
  • 39

2 Answers2

2

Store the time when the script is supposed to end in the lock file.

if [ -e "$LOCKFILE" ]; then
    read pid endtime < "$LOCKFILE"
    if kill -0 "$pid"; then
        notify-send "Already running" $(($(date +%s) - $endtime))
        exit
    fi
fi

trap "rm -f ${LOCKFILE}" EXIT   # Use cascaded trap
trap 'exit 127' INT TERM

echo $$ $(($(date +%s) + (60 * $TIME))) >"$LOCKFILE"

There is a race condition here; if two scripts are started at almost the same time, the first could be inside the if but before the echo when the second starts. If you really need to prevent that, use a lock directory instead of a file -- directory creation is atomic, and either succeeds or fails at just a single point in time (but then you'll need to clean out the stale directory in the mystery scenario where the directory exists but is not owned by a file -- maybe after a careless OOM killer or something).

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • 1
    Incidentally, with the `EXIT` trap, there is no need to explicitly remove the lock file at the end of the script. – tripleee Apr 02 '15 at 09:49
  • Nice one. works perfect. One modification though, the notify-send command cannot accept a negative number as the second argument it thinks as though a command line option is provided: so a little change to the fourth line in your code: `notify-send "Already running" "$(($endtime - $(date +%s))) seconds remaining"` – Merhawi Fissehaye Apr 02 '15 at 09:49
  • Then `-13 seconds remaining` is not a valid option name either. Perhaps change it to `"Seconds remaining: $(($endtime - $(date +%s)))"`... although if the result is negative, I suppose it ought to have finished already. – tripleee Apr 02 '15 at 09:51
  • or just put the result of the difference in a variable and insert it into the string as: `remaining=$(($endtime - $(date +%s))); notify-send "Already running" "$remaining seconds remaining"` – Merhawi Fissehaye Apr 02 '15 at 12:31
  • That still fails if `$remaining` begins with a dash, so it doesn't help. – tripleee Apr 02 '15 at 12:37
  • Yes that's right. I don't understand however, why you wanted to use cascade trap instead of putting them in one line? – Merhawi Fissehaye Apr 02 '15 at 12:54
0

I think Triplee has a fine answer, another way to handle it that can be applied to any running process that may block is to bg the process briefly to grab and save the assigned pid $! to a file then fg the process back.

From there you can do the math and get the seconds via ps:

TIME=$(zenity --scale --title="Next Session in (?) minutes")

SLEEP_PID_FILE="/tmp/__session_ui_sleep_pid__" 
sleep $TIME'm' &
echo $! >> "${SLEEP_PID_FILE}" 
fg 

BEEP="/usr/share/sounds/freedesktop/stereo/complete.oga"
paplay $BEEP
notify-send "Next Session" "Press <Ctrl><Shift><s> to run the script again"

Then afterward you can find the current elapsed time with something like:
notify-send "Already running for $(($(date +%s)-$(date -d"$(ps -o lstart= -p$(< "${SLEEP_PID_FILE}"))" +%s))) seconds..."