Como remover novas linhas entre dados de cada registro que estão localizados entre dois padrões?

5

Eu tenho um arquivo grande para analisar e reformatar, de preferência com sed (sob bash). O arquivo contém seqüências repetitivas começando com PATTERN_START e terminando com PATTERN_END . Essas seqüências são misturadas com outro texto que eu tenho que manter inalterado. Nas sequências, existem vários registros (numerados de 1 a n , onde n pode ser de 1 a 12). Um registro é um grupo de linhas que começam com uma linha no formato Record i , em que i é um número inteiro entre 1 e n e termina com outra linha ( Record (i+1) ) ou uma linha PATTERN_END . A duração de um registro pode ser de 1 linha a 30 linhas.

Aqui está uma representação genérica de um arquivo de entrada:

unrelated data          (possibly many lines)                      ⎤
PATTERN_START                                                      |
Record 1                                  ⎤                        |
data for Record 1(up to 30 lines)    |                        |  (many repetitions)
      ︙           ⎦                      |  (up to 12 records)    |
Record 2                                 |                        |
data for Record 2                        ⎦                         |
PATTERN_END                                                       ⎦
unrelated data          (possibly many lines)

Então, gostaria que, APENAS para os registros localizados entre PATTERN_START e PATTERN_END , tivessem todas as linhas de dados de cada registro reunidas na linha Record .

Alguém pode ajudar?

Abaixo está uma amostra do arquivo que preciso analisar e o tipo de resultado que gostaria de ter:

Entrada

Blabla
Blabla
PATTERN_OTHER
Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
PATTERN_END
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Record 3         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Data
PATTERN_END
Blabla
Blabla
Blabla
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
PATTERN_END
Blabla
Blabla
PATTERN_OTHER
Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
Record 2         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
PATTERN_END
Blabla
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
PATTERN_END
Blabla
Blabla

Saída

Blabla
Blabla
PATTERN_OTHER
Record 1         <- was not between PATTERN_START and PATTERN_END tags => not modified
Data
Data
PATTERN_END
Blabla
PATTERN_START
Record 1 Data Data Data        <- record data grouped in one line
Record 2 Data Data             <- record data grouped in one line
Record 3 Data Data Data Data   <- record data grouped in one line
PATTERN_END
Blabla
Blabla
Blabla
Blabla
PATTERN_START
Record 1 Data Data Data        <- record data grouped in one line
PATTERN_END
Blabla
Blabla
PATTERN_OTHER
Record 1         <- was not between PATTERN_START and PATTERN_END tags => not modified
Data
Data
Record 2         <- was not between PATTERN_START and PATTERN_END tags => not modified
Data
PATTERN_END
Blabla
Blabla
PATTERN_START
Record 1 Data                  <- record data grouped in one line
Record 2 Data Data Data        <- record data grouped in one line
PATTERN_END
Blabla
Blabla
    
por Syl87 22.06.2015 / 12:11

5 respostas

7

Pense que isso é o que você quer usando o GNU sed

 sed -n '/^PATTERN_START/,/^PATTERN_END/{
         //!{H;/^Record/!{x;s/\n\([^\n]*\)$/ /;x}};
         /^PATTERN_START/{h};/^PATTERN_END/{x;p;x;p};d
         };p' file

Explicação

sed -n #Non printing


'/^PATTERN_START/,/^PATTERN_END/{
#If the line falls between these two patterns execute the next block

  //!{
  #If the previous pattern matched from the line above is not on matched(so skip 
         the start and end lines), then execute next block

        H;
        #append the line to the hold buffer, so this appends all lines between 
       #'/^PATTERN_START/' and '/^PATTERN_END/' not including those.

        /^Record/!{
        #If the line does not begin with record then execute next block

            x;s/\n\([^\n]*\)$/ /;x
            #Swap current line with pattern buffer holding all our other lines 
            #up to now.Then remove the last newline. As this only executed when 
            #record is not matched it just removes the newline from the start 
            #of 'data'.
            #The line is then put switched back into the hold buffer.

        }
        #End of not record block

    }; 
    #End of previous pattern match block

    /^PATTERN_START/{h};

    #If line begins with 'PATTERN_START' then the hold buffer is overwritten 
    #with this line removing all the previous matched lines.

    /^PATTERN_END/{x;p;x;p}
    #If line begins with 'PATTERN_END' the swap in our saved lines, print them,
    #then swap back in the PATTERN END line and print that as well.

    ;d
    #Delete all the lines within the range, as we print them explicitly in the 
    #Pattern end block above


         };p' file
         # Print everything that's not in the range print, and the name of the file
    
por 22.06.2015 / 12:29
6

Isso foi o melhor que consegui:

sed -n '/^PATTERN_START/, /^PATTERN_END/{
            /^PATTERN_START/{x;s/^.*$//;x};
            /^Record/{x;/^\n/{s/^\n//p;d};s/\n/ /gp};
            /^PATTERN_END/{x;/^\n/{s/^\n//p;d};s/\n/ /gp;g;p};
            /^Record/!H
        };   
        /^PATTERN_START/, /^PATTERN_END/!p'

Explicação

Suponho que você esteja familiarizado com a ideia de manter espaço e espaço padrão em sed . Nesta solução, faremos muitas manipulações no espaço padrão. Assim, o primeiro ponto é desativar a impressão automática com a opção -n e imprimir onde for necessário.

A primeira tarefa é juntar todas as linhas que estão entre Record lines.

Considere o seguinte arquivo:

a
b
Record 1
c
d
Record 2
e
f
Record 3

Depois de juntar linhas, queremos que seja

a
b
Record 1 c d
Record 2 e f
Record 3

Então, aqui está o plano:

  1. Lemos uma linha, anexamos ao espaço de espera.
  2. Se a linha começar com Record , significa que o registro anterior foi concluído e um novo registro foi iniciado. Então, imprimimos o espaço de espera, limpamos e começamos com o ponto 1 novamente.

O ponto 1 é implementado pelo código /^Record/!H (quinta linha no comando). O que significa é "se a linha não começar com Record , adicione uma nova linha ao espaço de espera e anexe esta linha ao espaço de espera".

O ponto 2 pode ser implementado pelo código     / ^ Gravar / {x; s / \ n / / gp;} onde x substitui os espaços de padrão e de espera, s substitui todos \n s por s e p flag imprime o espaço de padrão. O uso de x também tem a vantagem de que agora o espaço de espera contém a linha Record atual para que possamos começar outro ciclo de pontos 1 e 2.

Mas isso tem um problema. No exemplo dado, existem duas linhas     uma     b antes da primeira linha Record . Não queremos substituir \n por nessas linhas. Como eles não começam com Record , de acordo com o ponto 1, \n é adicionado para manter o espaço e, em seguida, essas linhas são anexadas. Portanto, se o primeiro caractere do espaço de suspensão for \n , isso significa que não foi encontrado Record antes e não devemos substituir \n por . Isso é feito com o comando

/^\n/{s/^\n//p;d}

Assim, todo o comando se torna

/^Record/{x;/^\n/{s/^\n//p;d};s/\n/ /gp};

Agora, a segunda complicação é que queremos unir linhas, mesmo que uma linha Record não seja terminada por uma linha Record , mas por uma linha PATTERN_END . Queremos fazer exatamente as mesmas coisas do ponto 2, mesmo quando a linha começa com PATTERN_END . Então o comando se torna

/^PATTERN_END/{x;/^\n/?s/^\n//p;d};s/\n/ /gp}

Mas há um problema com isso. Como no caso de Record lines, a linha PATTERN_END agora acaba no espaço de espera. Mas sabemos que não haverá mais junção de linhas após PATTERN_END line. Então, podemos imprimir isso. Então, nós trazemos a linha PATTERN_END para o espaço padrão com g e imprimimos com p . Então o comando final se torna

/^PATTERN_END/{x;/^\n/?s/^\n//p;d};s/\n/ /gp;g;p}

O próximo problema é com as linhas PATTERN_START . Na explicação acima, assumimos que no início, o espaço de espera está vazio. Mas depois de um PATTERN_END , há algo no espaço de espera. (Esse algo é apenas PATTERN_END line). Quando começamos um novo ciclo com PATTERN_START , queremos limpar o espaço de espera.

Então, o que fazemos é quando encontramos PATTERN_START , trocamos o conteúdo dos espaços hold e pattern, limpamos o espaço padrão e trocamos novamente. Isso faz com que o espaço fique limpo. Isso é exatamente o que o seguinte comando faz:

/^PATTERN_START/{x;s/^.*$//;x}

O último traço é que queremos fazer todo esse trabalho apenas entre PATTERN_START e PATTERN_END linhas. Outros, nós apenas os imprimimos. Isso é feito pelos comandos

/^PATTERN_START/, /^PATTERN_END/{
    ----above commands go here----
};
/^PATTERN_START/, /^PATTERN_END/!p

Coloque tudo isso junto e isso dá o comando final:)

    
por 22.06.2015 / 17:27
3

Outras maneiras com sed :

sed '/PATTERN_START/,/PATTERN_END/{   # in this range
//!{                                  # if not start or end of range
/^Record/{                            # if line matches Record
x                                     # exchange pattern and hold space
/^$/d                                 # if pattern space is empty, delete it
s/\n/ /g                              # replace newlines with spaces
}
/^Record/!{                           # if line doesn't match Record
H                                     # append it to hold space
d                                     # then delete it
}
}
/PATTERN_END/{                        # at end of range
x                                     # exchange pattern and hold space
s/\n/ /g                              # replace newlines with space
G                                     # append hold space to pattern space
x                                     # exchange again
s/.*//                                # empty pattern space
x                                     # exchange again > empty line in hold space
}
}' infile

ou

sed '/PATTERN_START/,/PATTERN_END/{     # same as above
//!{                                    # same as above
: again
N                                       # pull the next line into pattern space
/\nRecord/!{                            # if pattern space doesn't match this
/\nPATTERN_END/!{                       # and doesn't match this either
s/\n/ /                                 # replace newline with space
b again                                 # go to : again
}
}
P                                       # print up to first newline
D                                       # then delete up to first newline
}
}' infile

Em uma linha:

sed '/PATTERN_START/,/PATTERN_END/{//!{/^Record/{x;/^$/d;s/\n/ /g};/^Record/!{H;d}};/PATTERN_END/{x;s/\n/ /g;G;x;s/.*//;x}}' infile

e

sed '/PATTERN_START/,/PATTERN_END/{//!{: again;N;/\nRecord/!{/\nPATTERN_END/!{s/\n/ /;b again}};P;D}}' infile
    
por 22.06.2015 / 19:04
2

Um caminho Perl:

perl -lne 'if(/^PATTERN_START/){$i=1; %data=(); print; next};
           if(/^PATTERN_END/){
             $i=0;
             if(defined($r)){
               print "$_ @{$data{$_}}" for sort keys(%data)
             }         
           }
           if($i==1){
            if(/^(Record\s*\d+)/){$r=$1;}
            else{push @{$data{$r}},$_}
           }   
           else{print}' file

E a mesma coisa que um script comentado:

#!/usr/bin/env perl

## Read the input file line by line (this is the same as the '-n' flag)
while (<>) {
    ## If this line matches the start pattern, set $i to 1, clear
    ## the %data hash, print the line and skip to the next one.
    if(/^PATTERN_START/){
        $i=1;
        %data=();
        print;
        next
    }
    ## If this line matches the end pattern, set $i to 0
    if(/^PATTERN_END/){
        $i=0;
        ## If we're at the end pattern, we may have saved some
        ## data. This is the case if $r is defined.
        if(defined($r)){
            ## Print the current line, then the saved data associated
            ## with each of the records of this section.
            print "$_ @{$data{$_}}\n" for sort keys(%data)
        }          
    }
    ## If $i is 1, if we're inside a START and END pattern.
    if($i==1){
        ## Remove trailing newlines. This is equivallent to '-l' with
        ## the difference that '-l' also adds a newline to each print()
        ## call. We will add them manually here. 
        chomp;
        ## If this line starts with Record, 0 or more spaces and one
        ## or more numbers, save that as $r. 
        if(/^(Record\s*\d+)/){$r=$1;}
        ## If this line does not start with the above pattern, add it
        ## to the data saved for the current record. 
        else{push @{$data{$r}},$_}
    }
    ## For all other lines, print them as is. 
    else{print}
}
    
por 22.06.2015 / 16:44
0

Eu fiz três versões disso.

v1

sed     -e'/^PATTERN_START/!b'  -e:n -eN  \
        -e'/\nPATTERN_END$/!bn' -eh\;s/// \
        -e'x;s/\n[[:print:]]*$//;x'       \
        -e's/\(\nRecord [[:print:]]*\)\{0,1\}\n/ /g'  \
        -e'G;P;D'       data

Aquele imprime o arquivo todo depois de aplicar apenas as edições às linhas Record que ocorrem entre PATTERN_{START,END} .

v2

sed   -ne'/\n/P;:n'    \
       -e'/^PATTERN_[OS]/!D'   -eN     \
       -e'/\nPATTERN_END$/!bn' -es///  \
       -e'/^PATTERN_S/s/\(\nRecord [[:print:]]*\)\{0,1\}\n/ /g'      \
       -eG\;D  ./data                 ###<gd data> haha

Esse imprime Record linhas dentro de PATTERN_{(START|OTHER),END} , mas só aplica edições àquelas que ocorrem entre PATTERN_{START,END} .

v3

sed   -ne'/\n/P;:n'    \
       -e'/^PATTERN_START/!D'  -eN     \
       -e'/\nPATTERN_END$/!bn' -es///  \
       -e's/\(\nRecord [[:print:]]*\)\{0,1\}\n/ /g'      \
       -eG\;D  ./data

E essa única somente edição e somente imprime Record linhas que ocorrem entre PATTERN_{START,END} .

O que se segue é a saída de cada um após ter executado sua amostra de entrada por meio dele. As amostras de saída são apresentadas em ordem inversa, do menor até o maior.

v3

Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data
Record 3         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data Data
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data

v2

Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data
Record 3         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data Data
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
Record 2         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data

v1

Blabla
Blabla
PATTERN_OTHER
Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
PATTERN_END
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data
Record 3         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data Data
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Record 3         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Data
Blabla
Blabla
Blabla
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Blabla
Blabla
PATTERN_OTHER
Record 1         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
Data
Record 2         <- record not between PATTERN_START and PATTERN_END tags => do not touch it
Data
PATTERN_END
Blabla
Blabla
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line Data Data Data
PATTERN_START
Record 1         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Record 2         <- record between PATTERN_START and PATTERN_END tags => to put in one line
Data
Data
Data
Blabla
Blabla
    
por 29.07.2015 / 19:45

Tags