Solved

Regex output for data return from OSX LAST command

Posted on 2014-04-13
23
449 Views
Last Modified: 2014-04-13
OSX's LAST command will provide user login history and example output  is shown below:

fred    ttys002                   Tue Apr  8 21:23   still logged in
john    ttys003  192.168.1.61     Sat Mar 29 13:34 - 20:51  (07:17)
ruth    ttys002  192.168.1.61     Sat Mar 29 12:07 - 12:13  (23:06)
teddy    ttys002  192.168.1.61     Sat Mar 29 12:02 - 12:06  (00:04)
teddy    ttys002  192.168.1.61     Sat Mar 29 11:58 - 11:58  (00:00)
tint    ttys000                   Wed Mar 12 21:28   still logged in
reggie    ttys001                   Wed Mar 12 21:28   still logged in
sylvia    console                   Tue Mar 11 19:21   still logged in
reboot    ~                         Tue Mar 11 19:13
shutdown  ~                         Tue Mar 11 19:12
sylvia    ttys001                   Mon Feb 24 09:25 - 19:12 (15+09:46)
ronnie    ttys000                   Mon Feb 24 09:25 - 19:12 (15+09:46)
brutm    console                   Mon Feb 24 08:25 - 19:12 (15+10:46)
reboot    ~                         Mon Feb 24 07:11
shutdown  ~                         Mon Feb 24 07:09
brutm    ttys001                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    ttys000                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    console                   Thu Feb  6 07:16 - 07:09 (17+23:53)



I would like a regex which would provide the details for each column so that I can store these in a database and cater for the situation where the user is still logged in or the end time/duration is known and where there's no IP address.

Any suggestions or solutions welcomed.
0
Comment
Question by:BJM1M
  • 11
  • 8
  • 4
23 Comments
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997540
The regex part is easy:
^(\w+)\s+([\w~]+)\s+([\d\.]+)?\s*(\w+\s+\w+\s+\d+\s+\d+:\d+)[\s-]+(still logged in)?(\d+:\d+\s+\(.+\))?$

Open in new window

You'll get:
in $1: username
in $2: console
in $3: IP - can be empty
in $4: last login
in $5: the string "still logged in" - can be empty
in $6: logout time (the 07:09 (17+23:53) part) - can be empty

HTH,
Dan
0
 

Author Comment

by:BJM1M
ID: 39997615
Thanks Dan, I'm trying to incorporate this into C# in order to test, so my first stepwas to test the expression against the text.

I used various online regent test sites but no match was found.  An example site that I used was http://www.freeformatter.com/regex-tester.html - no match found.

Any ideas?
0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997619
In C# you need to prepend your regex with an @, I think:
@"^(\w+)\s+([\w~]+)\s+([\d\.]+)?\s*(\w+\s+\w+\s+\d+\s+\d+:\d+)[\s-]+(still logged in)?(\d+:\d+\s+\(.+\))?$"

Open in new window


I tested my regex with RegexBuddy. Let's see what the online testers are saying.
0
Simplifying Server Workload Migrations

This use case outlines the migration challenges that organizations face and how the Acronis AnyData Engine supports physical-to-physical (P2P), physical-to-virtual (P2V), virtual to physical (V2P), and cross-virtual (V2V) migration scenarios to address these challenges.

 

Author Comment

by:BJM1M
ID: 39997623
Hi Dan,

yes I've done the @ prefix  in my C# project but I get the same result of no matches.
0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997626
Yup. I forgot the multiline option :)
@"(?m)^(\w+)\s+([\w~]+)\s+([\d\.]+)?\s*(\w+\s+\w+\s+\d+\s+\d+:\d+)[\s-]+(still logged in)?(\d+:\d+\s+\(.+\))?$"

Open in new window

The (?m) means: "^$ match at line breaks". Or simply multiline mode.
Check "Perform multiline matching" on your testing site and it will work there too.
0
 

Author Comment

by:BJM1M
ID: 39997671
Hi Dan,

it works great on the test sites, however when I run it in my C# code it only recognises four matches. I can accept your answer as it  does answer my specific question, please advise.

Now as you know how things go, it has raised another question in my C# code. I guess I should raise a new question?

Here's my code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace RegExConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            string txt = @"fred    ttys002                   Tue Apr  8 21:23   still logged in
john    ttys003  192.168.1.61     Sat Mar 29 13:34 - 20:51  (07:17)
ruth    ttys002  192.168.1.61     Sat Mar 29 12:07 - 12:13  (23:06)
teddy    ttys002  192.168.1.61     Sat Mar 29 12:02 - 12:06  (00:04)
teddy    ttys002  192.168.1.61     Sat Mar 29 11:58 - 11:58  (00:00)
tint    ttys000                   Wed Mar 12 21:28   still logged in
reggie    ttys001                   Wed Mar 12 21:28   still logged in
sylvia    console                   Tue Mar 11 19:21   still logged in
reboot    ~                         Tue Mar 11 19:13 
shutdown  ~                         Tue Mar 11 19:12 
sylvia    ttys001                   Mon Feb 24 09:25 - 19:12 (15+09:46)
ronnie    ttys000                   Mon Feb 24 09:25 - 19:12 (15+09:46)
brutm    console                   Mon Feb 24 08:25 - 19:12 (15+10:46)
reboot    ~                         Mon Feb 24 07:11 
shutdown  ~                         Mon Feb 24 07:09 
brutm    ttys001                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    ttys000                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    console                   Thu Feb  6 07:16 - 07:09 (17+23:53)
";

            
            

            string re1 = @"(?m)^(\w+)\s+([\w~]+)\s+([\d\.]+)?\s*(\w+\s+\w+\s+\d+\s+\d+:\d+)[\s-]+(still logged in)?(\d+:\d+\s+\(.+\))?$";
            Regex s = new Regex(re1, RegexOptions.IgnoreCase |RegexOptions.Singleline );
            
            foreach (Match m in s.Matches(txt))
            {
                //if (m.Success)
                //{
                    String var1 = m.Groups[1].ToString();
                    String var2 = m.Groups[2].ToString();
                    String ipaddress1 = m.Groups[3].ToString();
                    String var3 = m.Groups[4].ToString();
                    String var4 = m.Groups[5].ToString();
                    String day1 = m.Groups[6].ToString();
                    String time1 = m.Groups[7].ToString();
                    //String ext1 = m.Groups[8].ToString();
                    //String ext2 = m.Groups[9].ToString();
                    //String ext3 = m.Groups[10].ToString();

                    Console.Write("(" + var1.ToString() + ")" + "(" + var2.ToString() + ")" + "(" + ipaddress1.ToString() + ")" + "(" + var3.ToString() + ")" + "(" + var4.ToString() + ")" + "(" + day1.ToString() + ")" + "(" + time1.ToString() + ")" + "\n");
                //}
                Console.ReadLine();
            
            
            }
        }
    }




}

Open in new window

0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997680
Can you please post the output of your code?
0
 

Author Comment

by:BJM1M
ID: 39997691
Hi Dan,]

the output

first match:

(reboot)(~)()(Tue Mar 11 19:13)()()()

second match:

(shutdown)(~)()(Tue Mar 11 19:12)()()()

third match:

(reboot)(~)()(Mon Feb 24 07:11)()()()

fourth match:

(shutdown)(~)()(Mon Feb 24 07:09)()()()

Then the 'foreach' breaks and the program ends
0
 
LVL 34

Accepted Solution

by:
Dan Craciun earned 500 total points
ID: 39997710
OK, try this:
@"(?m)^(\w+)\s+([\w~]+)\s+([\d\.]+)?\s*(\w+\s+\w+\s+\d+\s+\d+:\d+)[\s-]*(still logged in)?(\d+:\d+\s+\(.+\))?\s*$"

Open in new window

I made the space after the login date optional and added some possible spaces at the end
0
 

Author Comment

by:BJM1M
ID: 39997717
The output is:
first match:

(john)(ttys003)(192.168.1.61)(Sat Mar 29 13:34)()(20:51  (07:17)

second match:

ruth    ttys002  192.168.1.61     Sat Mar 29 12:07 - 12:13  (23:06)
teddy    ttys002  192.168.1.61     Sat Mar 29 12:02 - 12:06  (00:04)
teddy    ttys002  192.168.1.61     Sat Mar 29 11:58 - 11:58  (00:00)
tint    ttys000                   Wed Mar 12 21:28   still logged in
reggie    ttys001                   Wed Mar 12 21:28   still logged in
sylvia    console                   Tue Mar 11 19:21   still logged in
reboot    ~                         Tue Mar 11 19:13
shutdown  ~                         Tue Mar 11 19:12
sylvia    ttys001                   Mon Feb 24 09:25 - 19:12 (15+09:46)
ronnie    ttys000                   Mon Feb 24 09:25 - 19:12 (15+09:46)
brutm    console                   Mon Feb 24 08:25 - 19:12 (15+10:46)
reboot    ~                         Mon Feb 24 07:11
shutdown  ~                         Mon Feb 24 07:09
brutm    ttys001                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    ttys000                   Fri Feb  7 17:14 - 07:09 (16+13:54)
brutm    console                   Thu Feb  6 07:16 - 07:09 (17+23:53))()


So, just two outputs
0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997719
Then I don't understand the problem, sorry.
I'm not a C# developer and I don't have an environment to test in.

But this is strange:
Regex s = new Regex(re1, RegexOptions.IgnoreCase |RegexOptions.Singleline );

IgnoreCase can stay, although you don't need it.
But Singleline? why? I specifically added the (?m) to force the multiline...

And what's the purpose of this: txt = @""?
I know you need the @ on the regex, but on the test string?
0
 

Author Comment

by:BJM1M
ID: 39997743
Hi Dan,

well spotted. I changed the RegexOptions.Singleline to multiline and that fixed the problem. To remove redundancy I then changed Regex s = new Regex(re1, RegexOptions.IgnoreCase |RegexOptions.Singleline ); to

Regex s = new Regex(re1);

and that worked.



The output is now working almost 100% correctly:


(fred)(ttys002)()(Tue Apr  8 21:23)(still logged in)()()

(john)(ttys003)(192.168.1.61)(Sat Mar 29 13:34)()(20:51  (07:17))()

(ruth)(ttys002)(192.168.1.61)(Sat Mar 29 12:07)()(12:13  (23:06))()

(teddy)(ttys002)(192.168.1.61)(Sat Mar 29 12:02)()(12:06  (00:04))()

(teddy)(ttys002)(192.168.1.61)(Sat Mar 29 11:58)()(11:58  (00:00))()

(tint)(ttys000)()(Wed Mar 12 21:28)(still logged in)()()

(reggie)(ttys001)()(Wed Mar 12 21:28)(still logged in)()()

(sylvia)(console)()(Tue Mar 11 19:21)(still logged in)()()

(reboot)(~)()(Tue Mar 11 19:13)()()()

(shutdown)(~)()(Tue Mar 11 19:12)()()()

(sylvia)(ttys001)()(Mon Feb 24 09:25)()(19:12 (15+09:46))()

(ronnie)(ttys000)()(Mon Feb 24 09:25)()(19:12 (15+09:46))()

(brutm)(console)()(Mon Feb 24 08:25)()(19:12 (15+10:46))()

(reboot)(~)()(Mon Feb 24 07:11)()()()

(shutdown)(~)()(Mon Feb 24 07:09)()()()

(brutm)(ttys001)()(Fri Feb  7 17:14)()(07:09 (16+13:54))()

(brutm)(ttys000)()(Fri Feb  7 17:14)()(07:09 (16+13:54))()

(brutm)(console)()(Thu Feb  6 07:16)()(07:09 (17+23:53))()


I say almost 100% because there's a missing parenthesis on the hours field e.g.
(brutm)(console)()(Thu Feb  6 07:16)()(07:09 (17+23:53))()

where should I insert the missing parenthesis in the Regex string?

Also, is there anyway with regex to change the date say Thu Feb 6 07:16 to dd/mm/yyyy hh:mm:ss.
0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997754
There isn't a missing parenthesis. Your text is formatted like:
07:09 (17+23:53), so the regular expression is picking exactly that. If you print time1 you'll see it's exactly that.

You cannot interpret date using regex. Define a function and convert var3 (I think that's the login date in your code) to whatever format you need.
Maybe with this: http://msdn.microsoft.com/en-us/library/cc165448.aspx
0
 

Author Comment

by:BJM1M
ID: 39997758
Thanks Dan - All good.

Many thanks for your help, appreciated
0
 

Author Closing Comment

by:BJM1M
ID: 39997759
Very helpful and patient.
0
 
LVL 34

Expert Comment

by:Dan Craciun
ID: 39997760
Glad I could help!
0
 
LVL 62

Expert Comment

by:gheist
ID: 39997774
Actually info you try to "store in the database" is already stored in /var/log/[uw]tmp DATABASES
Though I doubt you will be able to patch respective libc functions in closed source system to get output anywhere else.
That gives you - you must read particular files and live with only option available...
0
 

Author Comment

by:BJM1M
ID: 39997799
Thanks gheist I will examine the file to see what I can retrieve. I all else fails I'll continue with the LAST command . Basically what I'm trying to do is to get a record of login durations for each user, so right now I'm SSHing to each OSX device, running the LAST command, parsing and storing in to a database for subsequent analysis.
0
 
LVL 62

Expert Comment

by:gheist
ID: 39997848
Last reads that file. System has C headers to do that better
0
 

Author Comment

by:BJM1M
ID: 39997877
Ok but what I don't want to do it to have to deploy and run software on the osx device. Say a there are 100 osx devices, all I want is to be able to connect to each device  and pull this info without too much intervention. So, I thought that ssh and running a native command may be the most lightweight/efficient solution? Unless you can suggest a better way?

I've also been having difficulty in establishing when an osx devices keyboard has been locked and unlocked, this may seem off topic but it is indirectly related to this question.
0
 
LVL 62

Expert Comment

by:gheist
ID: 39997920
It is human-readable localized format....
You need to build your own tool to make it machine readable
From here: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/last/ or any other *BSD system
0
 
LVL 62

Expert Comment

by:gheist
ID: 39997989
If you ssh and run command you can as well scp executable. You also can make osx do syslog
0
 

Author Comment

by:BJM1M
ID: 39998070
Thanks , I shall do some investigation work and see how easy it will be to extract the information.
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

If you other experts are anything like me you are always looking into and testing out new features. While I was doing some research one day I ran across an app that I installed on my Mac and used as a security system. Mac OS X: SecureHome uses your …
The /etc/authorization file in Mac OS X 10.x can be used to control access to the various panes of the System Preferences amongst other things. It’s used by some of us Mac Sys Admin’s to give Standard Users access to System Prefs panes that only adm…
Learn how to get help with Linux/Unix bash shell commands. Use help to read help documents for built in bash shell commands.: Use man to interface with the online reference manuals for shell commands.: Use man to search man pages for unknown command…
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.

837 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question