Использование глубины пространства для анализа контекста конфигурационного файла с помощью Perl - PullRequest
0 голосов
/ 24 октября 2018

Я пытался создать синтаксический анализатор конфигурационных файлов для анализа конфигураций Cisco IOS и тому подобного.Конечная цель - показать соответствующие данные в контекстах на основе фильтров в файле конфигурации.Например, с таким файлом конфигурации он будет отображать все интерфейсы, в которых мы нашли строку «access vlan» как дочерний элемент контекста «interface», и отображать только строки, содержащие «speed», «duplex» и «description».

{
'Context' => '^interface',
'Types' => [
'Switch',
],
'Condition' => 'access vlan',
'Filter' => [
'speed',
'duplex',
'description'
]
};

Пока все хорошо.Я читаю "running-config" и индексирую глубину строк (учитывая, что непустая строка, не начинающаяся с пробела (\ s), имеет глубину 0) в массиве.

Затем,в другом чтении я использую этот индекс, чтобы снова прочитать данные, на этот раз используя относительное положение на основе глубины, чтобы создать «потомки» контекста.Вот функция:

sub getDeep { 

    my @data = (@_);
    my ($bighash,$hash); 

    #First read
    foreach my $idx (0.. $#data) { 

                    my ($spaces, $content) = ($data[$idx] =~  m/^(\s*)(.*)/); 
                    my $depth = length $spaces; 

                    $bighash->{node}{$idx}{depth} = $depth; 
    } 

    # Variables for the first read
    my $ldepth = 0; 
    my $lcontext; 
    my $lid; 

    # Second read
    foreach my $id (0 .. $#data) { 

                    $data[$id] =~ s/^\s*//; 
                    next if ($data[$id] =~ /^!/); 

                    my $depth = $bighash->{node}{$id}{depth}; 

                    if ($depth eq 0) { 
                                    push (@{$hash->{global}} , $data[$id]); 
                                    $lcontext = $data[$id]; 
                                    $lid = $id; 
                    } 

                    if (($depth gt 0) && ($id - $lid eq 1)) { 
                                    push (@{$hash->{$lcontext}}, (" " x $depth. $data[$id])); 
                                    $lid = $id; 
                    } 

    } 

    return $hash; 

}

Используя эту подпрограмму, я могу вернуть хеш, а затем, основываясь на наличии arrayref для данного ключа, применить фильтры, как объяснено.Это работает довольно хорошо, пока что очень горжусь этим фрагментом кода.

Проблема возникает, когда я хочу найти детей от детей.В приведенном ниже примере потомки «данного param2» представили бы мой следующий вызов.

interface XYZ
  given param1 -> child of "interface XYZ"
  given param2 -> child of "interface XYZ"
    given param2.1 -> child of "given param2"
    given param2.2 -> child of "given param2"
  given param3 -> child of "interface XYZ"

Поэтому, подумав об этом некоторое время и провалившись с разными подходами, мой вопрос состоит из 2 отдельных частей:

1) Есть ли лучший способ сделать это, чего я не вижу?2) Как можно помечать теги childs of childs, когда строки копают глубже и правильно идентифицируют их в структуре данных?

Спасибо, что прочитали эту строку:)

Ответы [ 2 ]

0 голосов
/ 26 октября 2018

В этой ветке содержится решение, на которое я надеялся :) Поскольку оно может принести пользу другим, вот ссылка:

https://perlmonks.org/?node_id=1224600

Приветствия!

0 голосов
/ 24 октября 2018

Я написал что-то, чтобы сделать именно это.Я не могу понять, как поместить это в metacpan.Тем не менее, я уверен, что там уже лучше, если бы я знал, где искать.Это первое, что я написал на Perl, так что это немного беспорядок.Но в основном вы можете набрать «gettree -l Node interface», и на устройстве XR он вытянет всю конфигурацию."gettree -s Node Description_keyword" будет извлекать все из одного интерфейса конфигурации.Вы также можете использовать его с STDIN, например, "cat file | gettree -l interface".

Программа

#!/usr/bin/perl

use lib '/PATH_TO_Gettree.pm_MODULE/';

use strict;
use warnings;
use Gettree;
use Getopt::Std;

my %opts;
getopts('eislnpm' , \%opts);


my $searchstr;
my $filename;
my $debug=0;

if($ARGV[0]=~m/help/ || $ARGV[0]=~m/^\?$/ )
{   die usage();    }

if($#ARGV<0||$#ARGV>1)
{
    usage();
    killapp("Please specifiy node and search term, use --help for the help menu");
}
elsif($#ARGV==0)
{
    Gettree::setopts( \%opts , \$ARGV[0] );
    while(<STDIN>)
    {
        Gettree::gettree_stream_passline( \$_ );
    }
    print Gettree::gettree_getreturnstring();
}
else
{
    $filename= $ARGV[0];
    $filename="/CONFIGS_DIR/".lc $filename if ! $opts{e};
    print Gettree::gettree_file ( \%opts , \$filename , \$ARGV[1]) ;  #\$filename , $searchstring

}



sub killapp
{

    print $_[0]."\n";
    exit;
}
sub usage
{
    print "
    Usage: gettree [OPTION]... [NODE] STRING
    Search for PATTERN in each FILE or standard input.

        usage gettree <options> <node> <string>
    -s      include same level
    -l      include lower levels
    -n      show line numbers (do not use with STDIN, it wont work)
    -i      case insensitive
    -e      exact file location (rather than just the nodename)
    -p      print parent's same level lines
    -m      minimal print, do not print parents
    Examples:
        gettree Node text
        gettree -sln NODE CCT_Ref
        gettree -l NODE POS8/0
    \n\n";
    exit;
}

Модуль

#!/usr/bin/perl


package Gettree;
use strict;
use warnings;


my $line;
my $wsdiff      = 0;
my $iopt        = 0;
my $sopt        = 0;
my $lopt        = 0;
my $nopt        = 0;
my $popt        = 0;
my $mopt        = 0;
my $linecounter = 0;
my $matched     = -1;
my $debug       = 0;    ##remove later
my @arr;
my @sopt_arr;
my @popt_arr;

my $searchstr;
my $returnstring;


sub setopts   # \%opthash , $searchstring
{
    cleardata();
    push @arr, [ 0, "",0];
    my %hash=%{$_[0]};

    $iopt   =   1 if $hash{i};
    $sopt   =   1 if $hash{s};
    $lopt   =   1 if $hash{l};
    $nopt   =   1 if $hash{n};
    $popt   =   1 if $hash{p};
    $mopt   =   1 if $hash{m};
    if ( defined $hash{qopts} )
    {
        $iopt = 1 if $hash{qopts} =~ /i/;
        $lopt = 1 if $hash{qopts} =~ /l/;
        $nopt = 1 if $hash{qopts} =~ /n/;
        $sopt = 1 if $hash{qopts} =~ /s/;
        $popt = 1 if $hash{qopts} =~ /p/;
        $mopt = 1 if $hash{qopts} =~ /m/;
    }
    if ( ref($_[1]) )   {   $searchstr=$iopt? qr/${$_[1]}/i : qr/${$_[1]}/ ;    }
    else                {   $searchstr=$iopt? qr/$_[1]/i : qr/$_[1]/ ;          }
}

sub gettree_stream_passline  # \$line
{
    process_line(${$_[0]});
}
sub gettree_getreturnstring
{
    return $returnstring;
}
sub gettree_varable         #  \%opthash , \$text , $searchstring
{
    setopts($_[0] , $_[2]);
    my $str=${$_[1]};
    while($str=~m#(.*\n)#g)
    {
        process_line($1);
    }
    return $returnstring;
}
sub gettree_file        #  \%opthash , \$filename , $searchstring
{
    setopts($_[0] , $_[2]);
    my $filename;   
    if ( ref($_[1]) )   {   $filename=${$_[1]}; }
    else                {   $filename=$_[1] ;   }
    open FH, "<", $filename or die "\nFile ".$filename." cannot be found\nerror : ".$!."\n";

    while(my $text=<FH>)
    {
        process_line($text);
    }

    close FH;
    return $returnstring;
}

sub process_line
{
    $line=shift;
    if($line=~m/^([ \t]+)/) {   $wsdiff=length($1)  }
    else {              $wsdiff=0       };

    if($wsdiff>$arr[$#arr][0])
    {
        push @arr, [ $wsdiff , $line , $linecounter ];
        if ( $sopt || $popt )
        {
            @popt_arr=@sopt_arr if $popt;
            @sopt_arr=() if defined $sopt_arr[0];
        }
    }
    else
    {
        while( @arr && $arr[$#arr][0]>$wsdiff )
        {
            pop @arr;
            @sopt_arr=@popt_arr if ( $sopt || $popt );
            @popt_arr=() if $popt;
        }
        if($#arr<0)
        {
            push @arr, [ $wsdiff , $line, $linecounter ];
        }
        else
        {
            push @sopt_arr, $arr[$#arr] if $sopt || $popt ;
            $arr[$#arr]=[ $wsdiff , $line , $linecounter ];
        }
    }
@sopt_arr=() if $#sopt_arr>200;  ## to avoid filling the memory
@popt_arr=() if $#popt_arr>200; ## to avoid filling the memory
    ##used in l and s opts to print lines after match
    if($matched>=0)
    {
        if($wsdiff>$matched)
        {
            printline(\$line) if $lopt==1 ; 
        }
        elsif ($wsdiff<$matched)
        {
            $matched=-1;
        }
        else
        {
            if ($sopt )
            { printline(\$line) }
            else
            { $matched=-1  }
        }
    }
    if( $matched==-1 && $line=~m/$searchstr/ )
    {
        printtree();
        $matched=$wsdiff if $sopt || $lopt;
    }
    $linecounter++;

}
sub printtree 
{   
    if(!$mopt)
    {
        for (0..$#arr-(1+$popt))
        {
            printline( \$arr[$_][1] , \$arr[$_][2] );
        }
    }
    if($popt)
    {
        for (0..$#popt_arr)
        {
            printline( \$popt_arr[$_][1] , \$popt_arr[$_][2] );
        }
        printline(  \$arr[$#arr-1][1] , \$arr[$#arr-1][2] ); #print the parent
        @popt_arr=() ;
    }
    if($sopt)
    {
        for (0..$#sopt_arr)
        {
            printline( \$sopt_arr[$_][1] , \$sopt_arr[$_][2] );
        }
        @sopt_arr=() ;
    }
    printline( \$arr[$#arr][1] , \$arr[$#arr][2] );
    @arr=();
    push @arr, [ $wsdiff , $line , $linecounter ];
}


sub printline
{
    $nopt==1?   $returnstring.= ${$_[1]}+1 ." : ".${$_[0]}  :   $returnstring.= ${$_[0]};
}

sub cleardata
{
    $line="";
    $wsdiff         = 0;
    $iopt        = 0;
    $sopt        = 0;
    $lopt        = 0;
    $nopt        = 0;
    $popt        = 0;
    $mopt        = 0;
    $linecounter = 0;
    $matched     = -1;
    @arr=();
    @sopt_arr=();
    @popt_arr=();
    $searchstr="";
    $returnstring="";
}
1;

Краткое объяснение, как это работает Программа представляет собой просто ссылку на модуль.Я модуль, потому что я использовал его во многих программах и в одиночку.Gettree.pm будет отправлять данные построчно в process_line ().строка процесса получит пробел ($ wsdiff) и будет использовать его как маркер.любая строка непосредственно перед приращением пробела будет сохранена в @arr.для печати, если совпадение найдено позже.так что родитель хранится.@sopt_arr - для той же строки, поэтому в ней хранятся предыдущие строки того же пробела.@popt_arr - для родительского соответствия, но это не очень хорошо работает (я действительно не использую его, его можно удалить).когда сопоставление для строки поиска выполнено, печатаются @ arr, Sopt_arr и & @popt_arr, устанавливается $ matched, это будет использоваться для опции -l.все строки после совпадения печатаются до тех пор, пока пробел не станет <совпадающим пробелом.Итак, в итоге, каждый прирост будет занимать каждый уникальный пробел.это работает для Juniper и Alcatel тоже.Я уверен, что это будет работать и на других.</p>

не забудьте изменить CONFIGS_DIR и PATH_TO_Gettree.pm_MODULE, чтобы они соответствовали путям вашей файловой системы

...