Hi everyone. I would like to write a script in bash that displays the total login time of every single user shown by unix "last" command, regardless of any different machines he connects from. How can I achieve that? Thank you.
Hi everyone. I would like to write a script in bash that displays the total login time of every single user shown by unix "last" command, regardless of any different machines he connects from. How can I achieve that? Thank you.
Generally, I would suggest you pipe the output through awk, and have it print whatever field(s) you want. Unfortunately, this doesn't work well because tthe last utility has a lot of variance in it's output, and everything seems to be space separated, not tab separated. For example, the reboot pseudo user has a tty entry of "system boot", so it's two words, which will screw up the column counts for awk. Same goes for the users that are "still logged in". You can try using the cut program to cut out the first however many characters. Something like
That will give you everything from the 65th character to the end. This isn't perfect, but on my system, I seem to always get sensible output, though I get "logged in" without the "still" part, if that's okay. I do, however, get leading white space on most time entries, which may be undesirable. You can clean that up easily enough by piping the result through sed, awk, cut and probably some other tools too.Code:last | cut -c65-
Hope that helps.Code:# For user 'corey', give me how many minutes he has been logged in with: total_minutes=0; for i in `last | grep corey | egrep -o '\([0-9]{2}:[0-9]{2}\)'` ; do i=`echo $i | egrep -o '[0-9]{2}:[0-9]{2}'` ; minutes=${i##*:}; hours=${i%%:*} ; minutes=$(echo $minutes | sed 's/^0//') ; hours=$(echo $hours | sed 's/^0//') ; let 'total_minutes+=hours*60+minutes' ; done ; echo $total_minutes
Nice job. I've simplified it a little. I'm assuming that the sed calls are unnecessary (the leading 0's don't matter). And I replaced the second egrep with another parameter expansion.
The two greps could probably be combined by using a sed or awk call instead, but I'm just learning this stuff so I'm not sure.Code:total_minutes=0 for i in `last | grep corey | grep -Eo '\([0-9]{2}:[0-9]{2}\)'` ; do minutes=${i##(*:} ; minutes=${minutes%%)} hours=${i%%:*)} ; hours=${hours##(} let 'total_minutes+=hours*60+minutes' done echo $total_minutes
The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss
Will a perl script do?
Code:#!/usr/bin/perl open LAST, "last |"; while (<LAST>) { if (($name,$days,$hours,$mins) = /^(\w+).+\((?:(\d+)\+)?(\d+):(\d+)/) { $TIMES{$name} += 1440 * $days + 60 * $hours + $mins; } } foreach (sort keys %TIMES) { print "$_ $TIMES{$_}\n"; }
The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss
No, it has to be in bash. Thanks anyway.
Then call the perl program from a bash script.
I mean, if you're allowed to call awk from the bash script then why not perl?
Or are you not allowed to call awk? What about sed? Or grep?
awk would probably be a fine choice but I don't know it very well at all (I started using perl on windows about a year ago but am just now learning "unix").
At any rate, this is starting to sound like a homework assignment and you've been given more than enough hints to do it yourself. Although I should point out that I was wrong in post#4 about the leading zeroes not mattering (I believe it makes bash think the numbers are in octal). The revised script is
A problem with the above (and Kleid's code it was based on) is that it ignores the possibility of a time like 1+02:13 which means 1 day, 2 hours, 13 minutes. The perl script in my previous post handles that possibility.Code:total_minutes=0 for i in `last | grep corey | grep -Eo '\([0-9]{2}:[0-9]{2}\)'` ; do mins=${i##(*:} ; mins=${mins%%)} ; mins${mins##0} hours=${i%%:*)} ; hours=${hours##(} ; hours=${hours##0} let 'total_minutes+=hours*60+mins' done echo $total_minutes
As another hint, this command will give you a sorted list of the unique names:
You could use that in an outer loop to feed names to the above command.Code:last | awk '{print $1}' | sort | uniq`
The cost of software maintenance increases with the square of the programmer's creativity. - Robert D. Bliss
Thanks a lot! Problem solved! Now I would like to deal with another problem if I don't bother. How could I show the number of sessions for the past 10 days? An approximation would be, at first, to sort uniquely the output of last by the field of day number, but there is a complication when we go back up to the previous month. Any ideas with that?
Sorry for not following your suggestion, but meanwhile I had an inspiration. As you see, I haven't coped with the possibity there are days in time, yet.Code:users="$( last | grep -v "wtmp" | awk '{print $1}' | sort -u )" for user in $users; do totmin=0 qq=$( echo "$res" | grep "$user" | grep -v "still logged in" | awk '{print $NF}' | sed 's/[()]//g' ) for q in $qq; do min="$( echo $q | awk -F: -v hrs=0 -v min=0 '{hrs=hrs+$1; min=min+$2}END{min=hrs*60+min;print min}' )" echo $min totmin=$(( $totmin + $min )) done qq=$( echo "$res" | grep "$user" | grep "still logged in" | awk '{print $NF}' if [ -n qq ]; then for q in $qq; do min="$( echo $q | awk -F: -v hrs=0 -v min=0 '{hrs=hrs+$1; min=min+$2}END{min=hrs*60+min;print min}' )" hrs=$( date -u +%H ) mins=$( date -u +%M ) mins=$(( $(($hrs * 60)) + $mins )) min=$(( $mins - $min )) totmin=$(( $totmin + $min )) done fi echo "$user $totminmins" >> rep1.txt
I haven't . This is why I am asking.What ideas do you have?
Sorry, you are right. $res holds the output of "last".
Anyway, let's focus on the other problem now.Code:res=$( last )