3
\$\begingroup\$

This script tries to determine the time when you woke up.

Of course, it only applies if you turn your computer on not too long ago after getting out of bed.

If not, it might still be useful as an indicator of when you turned your computer on.

It works by getting information from journalctrl, sleep-resume dates and boot dates.

It then loops through them and tries to find when the difference is above a certain gap (5 hours by default, it can be configured with the -g flag).

Then it prints the time and "timeago" information of when you probably woke up.

#!/bin/bash

gap=18000

while [ ! $# -eq 0 ]
do
    case "$1" in
        --help | -h)
            printf "Version: 1.2.1\nFlags:\n\t-g or --gap: Specifies the chunks of time between two dates to determine when you probably woke up (default is 5)\n"
            exit
            ;;
        --gap | -g)
            gap=$(($2 * 3600))
            ;;
    esac
    shift
done

current_date=$(date +%s)
readarray -t sleep_dates < <(journalctl -o short-unix -t systemd-sleep | grep resumed | tail -50 | awk -F. '{print $1}')
readarray -t boot_dates < <(journalctl --list-boots | tail -50 | awk '{ d2ts="date -d \""$3" "$4" " $5"\" +%s"; d2ts | getline $(NF+1); close(d2ts)} 1' | awk 'NF>1{print $NF}')
dates=( "${sleep_dates[@]}" "${boot_dates[@]}" )
readarray -t sorted_dates < <(printf '%s\n' "${dates[@]}" | sort)

for (( i=${#sorted_dates[@]}-1 ; i>=0; i-- )); do
    diff=$((sorted_dates[i] - sorted_dates[i - 1]))
    diff2=$((sorted_dates[i - 1] - sorted_dates[i - 2]))

    if [ "$diff" -gt "$gap" ] && [ "$diff2" -lt "$gap" ]; then
        sdate=$(date --date @${sorted_dates[i]} +"%r")
        diff2=$((current_date - sorted_dates[i]))
        hours_ago=$(echo "scale=2; ${diff2}/3600" | bc)
        whole_hours=$(echo "(${hours_ago})/1" | bc)
        decimals=$(echo "${hours_ago}" | grep -Eo "\.[0-9]+$")
        minutes_ago=$(echo "(${decimals}*60)/1" | bc)

        if [ "$whole_hours" -eq "1" ];
        then
            shours="hour"
        else
            shours="hours"
        fi

        if [ "$minutes_ago" -eq "1" ];
        then
            sminutes="minute"
        else
            sminutes="minutes"
        fi

        message="${whole_hours} ${shours} and ${minutes_ago} ${sminutes} ago ( ${sdate} )"
        echo "$message"
        break
    fi
done
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

The thing with bash is more often then not... there is a tool that you can leverage to do what you want. When writing bash scripts, I try write as little bash as possible, and to leverage these tools where best possible to avoid bash (seems counter intuitive, but is the nature of bash to write as little bash as possible). For example have you heard of the command tuptime? (https://manpages.debian.org/testing/tuptime/tuptime.1.en.html)

This is what it looks like after a reboot (is packaged with apt):

$ sudo apt-get install tuptime
$ tuptime | grep "System downtime"
System downtime:    0.15 %   -   32 seconds

Is not a complete answer because does not cover the >5 hrs case above.

For reference, here is a run of tuptime

$ tuptime
System startups:    2   since   11:02:34 AM 02/05/2020
System shutdowns:   1 ok   <-   0 bad
System uptime:      99.85 %   -   5 hours, 56 minutes and 45 seconds
System downtime:    0.15 %   -   32 seconds
System life:        5 hours, 57 minutes and 17 seconds

Largest uptime:     5 hours, 45 minutes and 12 seconds   from   11:02:34 AM 02/05/2020
Shortest uptime:    11 minutes and 33 seconds   from   04:48:18 PM 02/05/2020
Average uptime:     2 hours, 58 minutes and 23 seconds

Largest downtime:   32 seconds   from   04:47:46 PM 02/05/2020
Shortest downtime:  32 seconds   from   04:47:46 PM 02/05/2020
Average downtime:   32 seconds

Current uptime:     11 minutes and 33 seconds   since   04:48:18 PM 02/05/2020

If wanting something more specific, I would also consider last command which gives you information about when people logged in or out (using http://man7.org/linux/man-pages/man1/last.1.html).

$ last reboot --time-format iso
reboot   system boot  4.15.0-76-generi 2020-02-05T16:48:22-0800   still running
reboot   system boot  4.15.0-76-generi 2020-02-05T11:02:38-0800 - 2020-02-05T16:47:57-0800  (05:45)
reboot   system boot  4.15.0-76-generi 2020-02-04T14:30:53-0800 - 2020-02-05T16:47:57-0800 (1+02:17)
reboot   system boot  4.15.0-76-generi 2020-02-03T15:28:38-0800 - 2020-02-04T14:30:29-0800  (23:01)
reboot   system boot  4.15.0-76-generi 2020-02-03T10:55:02-0800 - 2020-02-03T15:28:13-0800  (04:33)
\$\endgroup\$
1
\$\begingroup\$

Philosophy

You seemed to worry about the usefulness of this a bit when you said "it might still be useful..." It doesn't have to be useful! Was it fun? Did you learn something? I love personal data collection projects like this. It is amazing what it can lead to.

I also find it interesting to see the idea of "write as little bash as possible" in a code review. While shell scripting isn't the oasis in a technology desert that it was thirty years ago it is still a good way to get a variety of things done. I write python for my job but I often have bash wrapper scripts. Each language has its strengths and the more you learn about each of them the better general programmer you will be. And if you ignore what you should be doing in bash then it is amazing what you can do.

Good Parts

Yes, really, I'm going to review your code.... Starting with some things I like:

  • good indentation
  • decent variable names
  • using $( for command substitution
  • quoting defensively

Suggestions

  • Using [[ for conditionals is a best practice and will help you avoid some surprises. "With double square brackets you don’t need to escape parenthesis and unquoted variables work just fine even if they contain spaces (meaning no word splitting or glob expansion)." source
  • You don't need the inner square brackets in if [ "$diff" -gt "$gap" ] && [ "$diff2" -lt "$gap" ]; then . Combining that with the previous point would lead to if [[ "$diff" -gt "$gap" && "$diff2" -lt "$gap" ]]; then
  • Use a heredoc for your usage print-out so it isn't one long line. It will be much easier to maintain and expand your help info in a heredoc.
  • For the code

    if [ "$whole_hours" -eq "1" ];
    then
    

    you can put the then up next to the ; or just drop the ; since the line ending will end the if command. Also, quoting the 1 is unnecessary, but harmless.

  • How about some comments?
  • Try shellcheck. It has excellent advice.
  • Using #!/usr/bin/env bash in your shebang line might be a good idea.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.