Solved

Regex output for data return from OSX LAST command

Posted on 2014-04-13
23
436 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
 

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
Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 

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 61

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 61

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 61

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 61

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

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Exception Handling is in the core of any application that is able to dignify its name. In this article, I'll guide you through the process of writing a DRY (Don't Repeat Yourself) Exception Handling mechanism, using Aspect Oriented Programming.
In this article we will discuss some EI Capitan Mail app issues and provide some manual process to resolve them.
This video shows how to set up a shell script to accept a positional parameter when called, pass that to a SQL script, accept the output from the statement back and then manipulate it in the Shell.
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.

760 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

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now