Parsing scutil output with perl Parsing scutil output with perl json json

Parsing scutil output with perl


In Perl, you can use Marpa::R2, a Perl interface to Marpa, a general 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  }}EOIsay 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'                  ]                ]              ]            ],            '}'          ]        ]      ]    ],    '}'  ]


The solution with Marpa::R2 is actually a nice generic approach.However, I'm not so happy with the generated hash map, which is probably the toll one has to pay for the generic parser.

I've come up with the following code to get a more straight hash map:

use Data::Dumper;open(my $pipe, '-|', "scutil <<- end_scutil 2> /dev/nullopenshow State:/Network/Service/21AD96AA-AD28-4D5C-93C1-F343FD07DA60/IPv4closeend_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  }}

it produces this hashmap:

$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'        };