Extrair parágrafo separado com *** usando AWK

3

Eu tenho um arquivo como abaixo:

blablabla
blablabla
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3

blablabla
blablabla

Eu quero extrair o parágrafo com thingsIwantToRead . Quando tive que lidar com esse problema, usei AWK assim:

awk 'BEGIN{ FS="Separator above the paragraph"; RS="" } {print $2}' $file.txt | awk 'BEGIN{ FS="separator below the paragraph"; RS="" } {print $1}'

E funcionou.

Nesse caso, tentei colocar FS="***" , "\*{3}" , "\*\*" (não está funcionando porque o AWK trata como um asterisco normal), "\*\*" ou qualquer regex em que eu possa pensar, mas não está funcionando (não está imprimindo nada).

Você sabe por quê?

Se não, você sabe outra maneira de lidar com o meu problema?

Abaixo, um extrato do arquivo que quero analisar:

13.2000000000     , 3*0.00000000000       ,  11.6500000000     , 3*0.00000000000       ,  17.8800000000

Blablabla

  SATELLITE EPHEMERIS
     ===================
Output frame: Mean of J2000

       Epoch                  A            E            I           RA           AofP          TA      Flight Ang
*****************************************************************************************************************
2012/10/01 00:00:00.000     6998.239     0.001233     97.95558     77.41733     89.98551    290.75808    359.93398
2012/10/01 00:05:00.000     6993.163     0.001168     97.95869     77.41920    124.72698    274.57362    359.93327
2012/10/01 00:10:00.000     6987.347     0.001004     97.96219     77.42327    170.94020    246.92395    359.94706
2012/10/01 00:15:00.000     6983.173     0.000893     97.96468     77.42930    224.76158    211.67042    359.97311
 <np>
 ----------------
 Predicted Orbit:
 ----------------

 Blablabla

E eu quero extrair:

2012/10/01 00:00:00.000     6998.239     0.001233     97.95558     77.41733     89.98551    290.75808    359.93398
2012/10/01 00:05:00.000     6993.163     0.001168     97.95869     77.41920    124.72698    274.57362    359.93327
2012/10/01 00:10:00.000     6987.347     0.001004     97.96219     77.42327    170.94020    246.92395    359.94706
2012/10/01 00:15:00.000     6983.173     0.000893     97.96468     77.42930    224.76158    211.67042    359.97311

E o comando que tentei usar para obter os números após a linha de * 's:

'awk 'BEGIN{ FS="\*{2,}"; RS="" } {print $2}' file | awk 'BEGIN{ FS="<np>"; RS="" } {print $1}''
    
por JoVe 10.06.2015 / 11:55

4 respostas

6

Diga ao awk para imprimir entre os dois delimitadores. Especificamente:

awk '/\*{4,}/,/<np>/' file

Isso também imprimirá as linhas contendo os delimitadores, para que você possa removê-las com:

awk '/\*{4,}/,/<np>/' file | tail -n +2 | head -n -1

Como alternativa, você pode definir uma variável como true se uma linha corresponder ao primeiro delimitador e a false quando corresponder à segunda e só imprimir quando for verdadeira:

awk '/\*{4,}/{a=1; next}/<np>/{a=0}(a==1){print}' file

O comando acima definirá a para 1 se a linha atual corresponder a 4 ou mais * e também passará para a linha next . Isso significa que a linha *** nunca será impressa.

Isto foi em resposta à versão original, mal compreendida, da questão. Estou deixando aqui, pois pode ser útil em uma situação ligeiramente diferente.

Primeiro de tudo, você não quer o FS (separador de campo), você quer RS (separador de registro). Então, para passar um literal * , você precisa escapar duas vezes. Uma vez para escapar do * e uma vez para escapar da contrabarra (caso contrário, o awk tentará igualá-lo da mesma forma que \r ou \t ). Então, você imprime a segunda "linha":

$ awk -vRS='\*\*\*' 'NR==2' file

thingsIwantToRead1   
thingsIwantToRead2   
thingsIwantToRead3  

Para evitar as linhas em branco ao redor da saída, use:

$ awk -vRS='\n\*\*\*\n' 'NR==2' file
thingsIwantToRead1   
thingsIwantToRead2   
thingsIwantToRead3  

Observe que isso pressupõe um% *** após cada parágrafo, não apenas após o primeiro, conforme mostrado.

    
por 10.06.2015 / 12:04
6

Além da resposta do @ terdon, com awk (e sed) você pode usar o padrão de intervalo:

awk '/sep1/,/sep2/{print}' file

ou

sed -n '/sep1/,/sep2/p' file

imprimirá tudo (incluindo) sep1 e sep2 . Isso é:

~$ awk '/sep1/,/sep2/{print}' file
sep1
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
sep2

No seu caso:

~$ awk '/\*\*\*/,/^$/{print}' file
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
 

Você pode querer excluir a primeira e a última linha.

Por exemplo, com:

~$ sed -n '/\*\*\*/,/^$/p' file | sed '1d;$d'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3

ou

~$ awk '/\*\*\*/,/^$/{print}' file | awk 'NR>1&&!/^$/ {print}'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3

Se o seu parágrafo não for muito longo.

    
por 10.06.2015 / 12:15
4

Com sed , há duas maneiras de lidar com isso. Você pode selecionar inclusive ou exclusivamente . No seu caso, uma seleção inclusive significa imprimir todas as linhas começando com uma correspondência de '^*\*\*' até e incluindo uma das ^ *<np> (o que quer que seja) ou ^$ uma linha em branco.

Uma seleção inclusive pode ser especificada com qualquer uma das expressões de intervalo demonstradas nas outras respostas e envolve a especificação de um padrão iniciar a impressão aqui através de um todo o caminho por aqui padrão.

Uma seleção exclusiva funciona de maneira oposta. Ele especifica um padrão pare a impressão antes aqui até o padrão iniciar a impressão após aqui . Para os dados do seu exemplo - e permitindo um padrão pare de imprimir antes aqui que combine com qualquer linha em branco ou que <np> thing:

sed -e 'x;/^\( *<np>.*\)*$/,/^*\** *$/c\' -e '' <infile >outfile
  • %código%
    • troca os espaços de espera e padrão. Isso institui um look-behind - x é sempre uma linha atrás da entrada - e a primeira linha está sempre em branco.
  • %código%
    • Isso seleciona uma linha parar a impressão antes aqui que corresponde da cabeça à cauda zero ou mais ocorrências no grupo de correspondência. Dois tipos de linhas podem corresponder a zero ou mais ocorrências disso - uma linha em branco ou uma com qualquer número de espaços> no início da linha, seguida pela string sed .
  • %código%
    • Isso seleciona uma linha impressão inicial após aqui que é aberta com pelo menos um caractere asterisco /^\( *<np>.*\)*$/ e continua até o final da linha com apenas zero ou mais ocorrências do asterisco <np> e possivelmente fechado por qualquer número de espaços.
  • %código%
    • Esse /^*\** *$/ trava toda a seleção bloqueada para uma única linha em branco, pressionando todas as linhas indesejadas para a string * .

Assim, qualquer número de linhas que ocorrem antes de * e depois do primeiro c\' -e '' é sempre reduzido a apenas um espaço em branco, e somente o primeiro parágrafo após corresponde a c é impresso no stdout. Imprime ...


2012/10/01 00:00:00.000     6998.239     0.001233     97.95558     77.41733     89.98551    290.75808    359.93398
2012/10/01 00:05:00.000     6993.163     0.001168     97.95869     77.41920    124.72698    274.57362    359.93327
2012/10/01 00:10:00.000     6987.347     0.001004     97.96219     77.42327    170.94020    246.92395    359.94706
2012/10/01 00:15:00.000     6983.173     0.000893     97.96468     77.42930    224.76158    211.67042    359.97311 

Isso pressupõe que você queira lidar com qualquer número de ocorrências do padrão de parágrafo na entrada. Se você quer apenas o primeiro , desde que tenha o GNU EOF e que ^*\** *$ seja um arquivo regular, lseekable :

{   grep -xm1 '*\** *'        >&2
    sed -n '/^\( *<np>.*\)*$/q;p'
}   <infile 2>/dev/null >outfile

... também funcionará.

E, na verdade, acho que existem três maneiras. O terceiro pode parecer:

sed 'H;$!d;x;s/\(\n\*\** *\n\(\([0-9./: ]*\n\)*\)\)*.//g'

... que lê todo o arquivo e substitui globalmente todos os caracteres que não se encaixam nas especificações das linhas correspondentes. Ele imprime da mesma forma que antes, mas é difícil de escrever, e eles são apenas seguros em relação ao desempenho quando você equilibra os opcionais contra qualquer personagem.

    
por 10.06.2015 / 14:45
1

Versão atualizada com base na edição da pergunta:

Usando o Perl:

< inputfile perl -0777 -pe 's/.*[*]+\n(.*) <np>\n.*/$1/s' > outputfile
  • < inputfile : redireciona o conteúdo de inputfile para perl ' stdin
  • -0777 : força o Perl a fazer o slurp do arquivo inteiro de uma só vez ao invés de linha por linha
  • -p : força o Perl a imprimir as linhas
  • -e : força o Perl a ler uma linha de programa dos argumentos
  • > outputfile : redireciona o conteúdo de perl stdout para outputfile

Divisão do Regex :

  • s : afirma para executar uma substituição
  • / : inicia o padrão de pesquisa
  • .*[*]+\n : corresponde a qualquer número de qualquer caractere até o final de uma sequência que termina com um ou mais caracteres * imediatamente seguido por um caractere de nova linha
  • (.*) <np> : corresponde e agrupa qualquer número de qualquer caractere até qualquer caractere imediatamente seguido por <np>\n string
  • .* : corresponde a qualquer número de qualquer caractere
  • / : interrompe o padrão de pesquisa / inicia o padrão de substituição
  • $1 : substitui pelo grupo capturado
  • / : interrompe o padrão de substituição / inicia os modificadores
  • s : afirma tratar a string de entrada como uma única linha, forçando . a também corresponder aos caracteres da nova linha

Exemplo de saída:

~/tmp$ cat inputfile
13.2000000000     , 3*0.00000000000       ,  11.6500000000     , 3*0.00000000000       ,  17.8800000000

Blablabla

  SATELLITE EPHEMERIS
     ===================
Output frame: Mean of J2000

       Epoch                  A            E            I           RA           AofP          TA      Flight Ang
*****************************************************************************************************************
2012/10/01 00:00:00.000     6998.239     0.001233     97.95558     77.41733     89.98551    290.75808    359.93398
2012/10/01 00:05:00.000     6993.163     0.001168     97.95869     77.41920    124.72698    274.57362    359.93327
2012/10/01 00:10:00.000     6987.347     0.001004     97.96219     77.42327    170.94020    246.92395    359.94706
2012/10/01 00:15:00.000     6983.173     0.000893     97.96468     77.42930    224.76158    211.67042    359.97311
 <np>
 ----------------
 Predicted Orbit:
 ----------------

 Blablabla
~/tmp$ < inputfile perl -0777 -pe 's/.*[*]+\n(.*) <np>\n.*/$1/s'
2012/10/01 00:00:00.000     6998.239     0.001233     97.95558     77.41733     89.98551    290.75808    359.93398
2012/10/01 00:05:00.000     6993.163     0.001168     97.95869     77.41920    124.72698    274.57362    359.93327
2012/10/01 00:10:00.000     6987.347     0.001004     97.96219     77.42327    170.94020    246.92395    359.94706
2012/10/01 00:15:00.000     6983.173     0.000893     97.96468     77.42930    224.76158    211.67042    359.97311
~/tmp$ 

Versão original:

Usando o Perl:

< inputfile perl -0777 -pe 's/.*[*]{3}\n(.*\n)\n.*/$1/s' > outputfile
  • < inputfile : redireciona o conteúdo de inputfile para perl ' stdin
  • -0777 : força o Perl a fazer o slurp do arquivo inteiro de uma só vez ao invés de linha por linha
  • -p : força o Perl a imprimir as linhas
  • -e : força o Perl a ler uma linha de programa dos argumentos
  • > outputfile : redireciona o conteúdo de perl stdout para outputfile

Divisão do Regex :

  • s : afirma para executar uma substituição
  • / : inicia o padrão de pesquisa
  • .*[*]{3}\n : corresponde a qualquer número de qualquer caractere até o final de uma string ***\n
  • (.*\n)\n : combina e agrupa qualquer número de qualquer caractere até um caractere de nova linha imediatamente seguido por um caractere de nova linha
  • .* : corresponde a qualquer número de qualquer caractere
  • / : interrompe o padrão de pesquisa / inicia o padrão de substituição
  • $1 : substitui pelo grupo capturado
  • / : interrompe o padrão de substituição / inicia os modificadores
  • s : afirma tratar a string de entrada como uma única linha, forçando . a também corresponder aos caracteres da nova linha

Exemplo de saída:

~/tmp$ cat inputfile
blablabla
blablabla
***
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3

blablabla
blablabla
~/tmp$ < inputfile perl -0777 -pe 's/.*[*]{3}\n(.*\n)\n.*/$1/s'
thingsIwantToRead1
thingsIwantToRead2
thingsIwantToRead3
~/tmp$ 
    
por 10.06.2015 / 19:07