My boss wants to have a simple tool that lets her quickly see how long someone’s been logged in to our Linux server. I figure a quick shell script, but I’m not sure how to code it. Note: Everyone has to log out at the end of the day, so it’s always going to be no more than 8 hours.
Haven’t had a shell script programming question for a while so it’s a pleasure to dig in and figure out how to solve this with a short program. For those of you who don’t know, Unix and Linux systems have a command line programming environment built around the interactive login shell, hence the name “shell script”. In fact, Mac OS X systems also have a command line and the ability to run full shell scripts, a handy capability that I explore in my brand new book Learning Unix for Mac OS X, actually!
Anyway, you present an interesting problem but fortunately frame it a way that makes the calculation considerably easier: Where a script of this nature gets difficult is when users might have been logged in for days, weeks or months.
For your script, there are two data points we need to figure out time on system: the exact time that the user logged in to the system and the current time. We’re going to assume that both are on the same date, so people aren’t logging in at 11pm for 4 hours, for example.
A user’s login time comes from the who command. Like this:
$ who taylor console Mar 1 07:00 smith ttys000 Mar 1 08:15
The first task, therefore, is isolating an individual user’s login information, then extracting the time when they logged in (above, taylor logged in at 7:00 and smith logged in at 8:15). Since there’s no am/pm indicated, it’s safe to conclude that this is a 24 hour clock, so logging in at 8:15pm produces a display of 20:15 in this context.
Isolating an individual line involves the use of grep, but be careful, you’ll want to have a complex pattern otherwise a search for “t” will match both taylor and smith. Here’s the pattern we’ll use, with $1 representing the desired login name: “^$1 “. You left-root the pattern with the ‘^’ and require it to have a space appended, ensuring that it’s also the end of the name too (so “smit” doesn’t match “smith”).
Extracting the hour and minute is a bit more tricky because we’ll need to isolate the time field, then break it up into its component parts. This can be done with cut. First, to get the login time:
who | grep "^$1 " | cut -d\ -f5
The result of that for $1 being set to “smith” would be 08:15. Breaking that up further is another use of cut, but this time with a different field delimiter, the colon. To get just the login hour, for example:
... | cut -d: -f1
Since all of this needs to happen within a shell script, it makes the most sense to assign these as two variables we’ll call “lhour” and “lmin” for login hour and login minute, respectively. That ends up looking like this:
login=$(who | grep "^$1 " | head -1) lhour=$(echo $login | cut -d\ -f5 | cut -d: -f1) lmin=$(echo $login | cut -d\ -f5 | cut -d: -f2)
Notice I slipped something else in too, the head -1 in the first variable assignment. The purpose of that first line is to ensure that the hour and minute are extracted from the same matching line produced by who, just in case the user might log out during execution of the script. It’s also slightly faster because the who|grep only needs to happen once, not twice.
Now the easier part, getting the current hour and minute. Those can both be done by using output formats to the date command, fortunately:
hour=$(date +%H) min=$(date +%m)
Which means now it’s just math. Elapsed time is the current time minus the login time, which can be calculated a couple of ways. I’m going to be lazy and simply subtract login hour from base hour and login minute from base minute:
elapsed_hours=$(( $hour - $lhour )) elapsed_mins=$(( $min - $lmin ))
The problem with this solution is that what if they logged in at 9:45 and it’s now 10:00? 10-9 = 1, and 0-45 = -45, which is a bit confusing because they haven’t been logged in for 1 hour and -45 minutes. Well, sort of they have, though, because a negative value for elapsed minutes means that it’s less than the current hour calculation, so 1 hour – 45 minutes = 15 minutes, which is correct.
This can be codified in the script thusly:
if [ $elapsed_mins -lt 0 ] ; then elapsed_mins=$(( $elapsed_mins + 60 )) elapsed_hours=$(( $elapsed_hours - 1 )) fi
So that indeed we subtract 1 hour frmo the elapsed hours and normalize the elapsed minutes by adding 60 (so that -45 + 60 = 15, for example).
All that’s left now is to offer up some useful output:
echo "User $1 logged in at ${lhour}:$lmin, and it's currently ${hour}:$min" echo "Elapsed login time is $elapsed_hours hours and $elapsed_mins minutes"
So there you have it. Now it’s up to you to copy and paste all the individual blocks to get a fully functional shell script to keep your boss happy. 🙂
Saw this yesterday and printed :
last -F on linux. Also very helpful is ac -d -p . Milege will vary when you’re on Linux, MacOS and UNIX.
Cheers.
on linux … last -F
Great post. One note… For this to work on my Mac I had to tweak your current minutes statement to use a +%M for minutes. The +%m was pulling the calendar month (3) for me when run today (3/23/16).
Thanks,
Erik