• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 487
  • Last Modified:

Regex output for data return from OSX LAST command

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
BJM1M
Asked:
BJM1M
  • 11
  • 8
  • 4
1 Solution
 
Dan CraciunIT ConsultantCommented:
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
 
BJM1MAuthor Commented:
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
 
Dan CraciunIT ConsultantCommented:
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
BJM1MAuthor Commented:
Hi Dan,

yes I've done the @ prefix  in my C# project but I get the same result of no matches.
0
 
Dan CraciunIT ConsultantCommented:
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
 
BJM1MAuthor Commented:
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
 
Dan CraciunIT ConsultantCommented:
Can you please post the output of your code?
0
 
BJM1MAuthor Commented:
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
 
Dan CraciunIT ConsultantCommented:
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
 
BJM1MAuthor Commented:
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
 
Dan CraciunIT ConsultantCommented:
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
 
BJM1MAuthor Commented:
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
 
Dan CraciunIT ConsultantCommented:
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
 
BJM1MAuthor Commented:
Thanks Dan - All good.

Many thanks for your help, appreciated
0
 
BJM1MAuthor Commented:
Very helpful and patient.
0
 
Dan CraciunIT ConsultantCommented:
Glad I could help!
0
 
gheistCommented:
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
 
BJM1MAuthor Commented:
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
 
gheistCommented:
Last reads that file. System has C headers to do that better
0
 
BJM1MAuthor Commented:
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
 
gheistCommented:
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
 
gheistCommented:
If you ssh and run command you can as well scp executable. You also can make osx do syslog
0
 
BJM1MAuthor Commented:
Thanks , I shall do some investigation work and see how easy it will be to extract the information.
0

Featured Post

Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

  • 11
  • 8
  • 4
Tackle projects and never again get stuck behind a technical roadblock.
Join Now