Perl scutil output analysis

I would like to get information from the Mac OSX scutil using a perl script.

The result generated by this tool is somewhat similar to JSON, but so far I have not been able to find anything that could parse it.

Example:

 scutil > open > show State:/Network/Service/0F70B221-EEF7-4ACC-96D8-ECBA3A15F132/IPv4 <dictionary> { ARPResolvedHardwareAddress : 00:1b:c0:4a:82:f9 ARPResolvedIPAddress : 10.10.0.254 AdditionalRoutes : <array> { 0 : <dictionary> { DestinationAddress : 10.10.0.146 SubnetMask : 255.255.255.255 } 1 : <dictionary> { DestinationAddress : 169.254.0.0 SubnetMask : 255.255.0.0 } } Addresses : <array> { 0 : 10.10.0.146 } ConfirmedInterfaceName : en0 InterfaceName : en0 NetworkSignature : IPv4.Router=10.10.0.254;IPv4.RouterHardwareAddress=00:1b:c0:4a:82:f9 Router : 10.10.0.254 SubnetMasks : <array> { 0 : 255.255.255.0 } } 

I have already managed to get certain elements using regular expressions, but since I need to do several different searches, I am looking for a more reasonable, more general approach.

Now, before I reinvent the wheel by coding for another-perl-parser, I hope that someone recognizes this format and can give some advice on how to parse this, say, a nested perl hash map.

Any comments are welcome.

+6
source share
2 answers

In Perl, you can use Marpa :: R2 , the Perl interface for Marpa, a common BNF parser .

Here is a quick example:

 use 5.010; use strict; use warnings; use Data::Dumper; $Data::Dumper::Indent = 1; $Data::Dumper::Terse = 1; $Data::Dumper::Deepcopy = 1; use Marpa::R2; my $g = Marpa::R2::Scanless::G->new( { source => \(<<'END_OF_SOURCE'), :default ::= action => [ name, value] lexeme default = action => [ name, value] latm => 1 scutil ::= 'scutil' '> open' '> show' path '<dictionary>' '{' pairs '}' path ~ [\w/:\-]+ pairs ::= pair+ pair ::= name ':' value name ~ [\w]+ value ::= ip | mac | interface | signature | array | dict ip ~ octet '.' octet '.' octet '.' octet octet ~ [\d]+ mac ~ [a-z0-9][a-z0-9]':'[a-z0-9][a-z0-9]':'[a-z0-9][a-z0-9]':'[a-z0-9][a-z0-9]':'[a-z0-9][a-z0-9]':'[a-z0-9][a-z0-9] interface ~ [\w]+ signature ::= signature_item+ separator => [;] signature_item ::= signature_item_name '=' signature_item_value signature_item_name ~ [\w\.]+ signature_item_value ::= ip | mac dict ::= '<dictionary>' '{' pairs '}' array ::= '<array>' '{' items '}' items ::= item+ item ::= index ':' value index ~ [\d]+ :discard ~ whitespace whitespace ~ [\s]+ END_OF_SOURCE } ); my $input = <<EOI; scutil > open > show State:/Network/Service/0F70B221-EEF7-4ACC-96D8-ECBA3A15F132/IPv4 <dictionary> { ARPResolvedHardwareAddress : 00:1b:c0:4a:82:f9 ARPResolvedIPAddress : 10.10.0.254 AdditionalRoutes : <array> { 0 : <dictionary> { DestinationAddress : 10.10.0.146 SubnetMask : 255.255.255.255 } 1 : <dictionary> { DestinationAddress : 169.254.0.0 SubnetMask : 255.255.0.0 } } Addresses : <array> { 0 : 10.10.0.146 } ConfirmedInterfaceName : en0 InterfaceName : en0 NetworkSignature : IPv4.Router=10.10.0.254;IPv4.RouterHardwareAddress=00:1b:c0:4a:82:f9 Router : 10.10.0.254 SubnetMasks : <array> { 0 : 255.255.255.0 } } EOI say Dumper $g->parse( \$input, { trace_terminals => 0 } ); 

Output:

 \[ 'scutil', 'scutil', '> open', '> show', [ 'path', 'State:/Network/Service/0F70B221-EEF7-4ACC-96D8-ECBA3A15F132/IPv4' ], '<dictionary>', '{', [ 'pairs', [ 'pair', [ 'name', 'ARPResolvedHardwareAddress' ], ':', [ 'value', [ 'mac', '00:1b:c0:4a:82:f9' ] ] ], [ 'pair', [ 'name', 'ARPResolvedIPAddress' ], ':', [ 'value', [ 'ip', '10.10.0.254' ] ] ], [ 'pair', [ 'name', 'AdditionalRoutes' ], ':', [ 'value', [ 'array', '<array>', '{', [ 'items', [ 'item', [ 'index', '0' ], ':', [ 'value', [ 'dict', '<dictionary>', '{', [ 'pairs', [ 'pair', [ 'name', 'DestinationAddress' ], ':', [ 'value', [ 'ip', '10.10.0.146' ] ] ], [ 'pair', [ 'name', 'SubnetMask' ], ':', [ 'value', [ 'ip', '255.255.255.255' ] ] ] ], '}' ] ] ], [ 'item', [ 'index', '1' ], ':', [ 'value', [ 'dict', '<dictionary>', '{', [ 'pairs', [ 'pair', [ 'name', 'DestinationAddress' ], ':', [ 'value', [ 'ip', '169.254.0.0' ] ] ], [ 'pair', [ 'name', 'SubnetMask' ], ':', [ 'value', [ 'ip', '255.255.0.0' ] ] ] ], '}' ] ] ] ], '}' ] ] ], [ 'pair', [ 'name', 'Addresses' ], ':', [ 'value', [ 'array', '<array>', '{', [ 'items', [ 'item', [ 'index', '0' ], ':', [ 'value', [ 'ip', '10.10.0.146' ] ] ] ], '}' ] ] ], [ 'pair', [ 'name', 'ConfirmedInterfaceName' ], ':', [ 'value', [ 'interface', 'en0' ] ] ], [ 'pair', [ 'name', 'InterfaceName' ], ':', [ 'value', [ 'interface', 'en0' ] ] ], [ 'pair', [ 'name', 'NetworkSignature' ], ':', [ 'value', [ 'signature', [ 'signature_item', [ 'signature_item_name', 'IPv4.Router' ], '=', [ 'signature_item_value', [ 'ip', '10.10.0.254' ] ] ], [ 'signature_item', [ 'signature_item_name', 'IPv4.RouterHardwareAddress' ], '=', [ 'signature_item_value', [ 'mac', '00:1b:c0:4a:82:f9' ] ] ] ] ] ], [ 'pair', [ 'name', 'Router' ], ':', [ 'value', [ 'ip', '10.10.0.254' ] ] ], [ 'pair', [ 'name', 'SubnetMasks' ], ':', [ 'value', [ 'array', '<array>', '{', [ 'items', [ 'item', [ 'index', '0' ], ':', [ 'value', [ 'ip', '255.255.255.0' ] ] ] ], '}' ] ] ] ], '}' ] 
+4
source

A solution with Marpa :: R2 is actually a good general approach. However, I am not very happy with the created hash map, which probably should pay for a common parser.

I came up with the following code to get a more direct hash map:

 use Data::Dumper; open(my $pipe, '-|', "scutil <<- end_scutil 2> /dev/null open show State:/Network/Service/21AD96AA-AD28-4D5C-93C1-F343FD07DA60/IPv4 close end_scutil") or die $!; sub doParse { my ($type) =@ _; my $map; my @arr; while(<$pipe>) { chomp; if ($type eq "dictionary") { if (m/^<dictionary> \{/) { $map=doParse("dictionary"); } elsif (m/\s*([^:]+) : <(.*)> \{/) { $map->{$1}=doParse($2); } elsif (m/\s*([^:]+) : ([^\}]+)$/) { $map->{$1}=$2; } elsif (m/\}$/) { return $map; } else { print STDERR "$type parse error on $_"; } } elsif ($type eq "array") { if (m/\s*(\d+) : <(.*)> \{/) { $arr[$1]=doParse($2); } elsif (m/\s*(\d+) : ([^\}]+)$/) { $arr[$1]=$2; } elsif (m/\}$/) { return \@arr; } else { print STDERR "$type parse error on $_"; } } } return $map; } print Dumper(doParse("dictionary")); 1; __END__ 

With this input from scutil

 <dictionary> { ARPResolvedHardwareAddress : 00:1e:8c:72:27:d2 ARPResolvedIPAddress : 192.168.1.10 AdditionalRoutes : <array> { 0 : <dictionary> { DestinationAddress : 192.168.1.232 SubnetMask : 255.255.255.255 } 1 : <dictionary> { DestinationAddress : 169.254.0.0 SubnetMask : 255.255.0.0 } } Addresses : <array> { 0 : 192.168.1.232 } ConfirmedInterfaceName : en0 InterfaceName : en0 NetworkSignature : IPv4.Router=192.168.1.10;IPv4.RouterHardwareAddress=00:1e:8c:72:27:d2 Router : 192.168.1.10 SubnetMasks : <array> { 0 : 255.255.255.0 } } 

he creates this hash map:

 $VAR1 = { 'InterfaceName' => 'en0', 'Addresses' => [ '192.168.1.232' ], 'ARPResolvedHardwareAddress' => '00:1e:8c:72:27:d2', 'NetworkSignature' => 'IPv4.Router=192.168.1.10;IPv4.RouterHardwareAddress=00:1e:8c:72:27:d2', 'ARPResolvedIPAddress' => '192.168.1.10', 'AdditionalRoutes' => [ { 'SubnetMask' => '255.255.255.255', 'DestinationAddress' => '192.168.1.232' }, { 'DestinationAddress' => '169.254.0.0', 'SubnetMask' => '255.255.0.0' } ], 'Router' => '192.168.1.10', 'SubnetMasks' => [ '255.255.255.0' ], 'ConfirmedInterfaceName' => 'en0' }; 
0
source

All Articles