Como encontrar todos os padrões entre dois caracteres?

5

Estou tentando encontrar todos os padrões entre um par de aspas duplas. Vamos dizer que eu tenho um arquivo com o conteúdo parecido com o seguinte:

first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".

Eu quero abaixo das palavras como saída:

One
Two
Three
Four

Como você pode ver todas as strings na saída estão entre um par de aspas.

O que eu tentei, é este comando:

grep -Po ' "\K[^"]*' file

O comando acima funciona bem se eu tiver um espaço antes do primeiro par de " marks. Por exemplo, funciona se o meu arquivo de entrada contiver o seguinte:

first matched is "One". the second is here "Two "
and here are in second line " Three " "Four".

Eu sei que posso fazer isso com a combinação de vários comandos. Mas estou procurando por um comando e sem usá-lo por várias vezes. por exemplo: abaixo do comando

grep -oP '"[^"]*"' file | grep -oP '[^"]*'

Como posso conseguir / imprimir todos os meus padrões usando apenas um comando?

Responder a comentários: Não é importante para mim remover espaços em branco em torno do padrão correspondente dentro de um par de citações, mas seria melhor se o comando também o suportasse. e também meus arquivos contêm citações aninhadas como "foo "bar" zoo" . E todas as palavras citadas estão em linhas separadas e não são expandidas para várias linhas.

Obrigado antecipadamente.

    
por αғsнιη 12.11.2014 / 14:10

5 respostas

7

Antes de mais nada, a sua ideia grep -Po '"\K[^"]*' file falha porque grep vê as duas "One" e ". the second is here" entre aspas. Pessoalmente, eu provavelmente apenas faria

$ grep -oP '"[^"]+"' file | tr -d '"'
One
Two 
 Three 
Four

Mas isso são dois comandos. Para fazer isso com um único comando, você poderia usar um dos seguintes:

  1. Perl

    $ perl -lne '@F=/"\s*([^"]+)\s*"/g; print for @F' file 
    One
    Two 
    Three 
    Four
    

    Aqui, a matriz @F contém todas as correspondências da expressão regular (uma cotação, seguida por tantas não " quanto possível até a próxima " ). O print for @F significa apenas "imprimir cada elemento de @F .

  2. Perl

    $ perl -F'"' -lne 'for($i=1;$i<=$#F;$i+=2){print $F[$i]}' file 
    One
    Two 
     Three 
    Four
    

    Para remover espaços iniciais / finais de cada partida, use isto:

    perl -F'"' -lne 'for($i=1;$i<=$#F;$i+=2){$F[$i]=~s/^\s*|\s$//; print $F[$i]}' file 
    

    Aqui, o Perl está se comportando como awk . A opção -a faz com que ela divida automaticamente as linhas de entrada nos campos do caractere fornecido por -F . Como eu dei " , os campos são:

    $ perl -F'"' -lne 'for($i=0;$i<=$#F;$i++){print "Field $i: $F[$i]"}' file 
    Field 0: first matched is 
    Field 1: One
    Field 2: . the second is here
    Field 3: Two 
    Field 0: and here are in second line
    Field 1:  Three 
    Field 2: 
    Field 3: Four
    Field 4: .
    

    Como estamos procurando texto entre dois separadores de campo consecutivos, sabemos que queremos cada segundo campo. Então, for($i=1;$i<=$#F;$i+=2){print $F[$i]} imprimirá os que nos interessam.

  3. A mesma ideia, mas em awk :

    $ awk -F'"' '{for(i=2;i<=NF;i+=2){print $(i)}}' file 
    One
    Two 
     Three 
    Four
    
por terdon 12.11.2014 / 14:43
2

A chave é consumir as aspas em sua expressão. Difícil de fazer isso com um único comando grep. Aqui está um perl one-liner:

perl -0777 -nE 'say for /"(.*?)"/sg' file

Isso absorve toda a entrada e imprime a parte capturada da partida. Ele funcionará mesmo se houver uma nova linha dentro das aspas, embora seja difícil separar elementos com e sem novas linhas. Para ajudar com isso, use um caractere diferente como o separador de registro de saída, o caractere nulo por exemplo

perl -0777 -lne 'print for /"(.*?)"/sg} BEGIN {$\="
0000000   f   i   r   s   t  
perl -0777 -nE 'say for /"(.*?)"/sg' file
s e c o n d \n q u o 0000020 t e w i t h n e w l i n e
perl -0777 -lne 'print for /"(.*?)"/sg} BEGIN {$\="
0000000   f   i   r   s   t  %pre%   s   e   c   o   n   d  \n   q   u   o
0000020   t   e       w   i   t   h       n   e   w   l   i   n   e  %pre%
0000040   t   h   i   r   d  %pre%
0000046
"' <<DATA | od -c blah "first" blah "second quote with newline" blah "third" DATA
0000040 t h i r d %pre% 0000046
"' <<DATA | od -c blah "first" blah "second quote with newline" blah "third" DATA
%pre%     
por glenn jackman 12.11.2014 / 15:46
1

Isso poderia ser possível com o liner abaixo do grep e eu assumi que você tem aspas equilibradas.

grep -oP '"\s*\K[^"]+?(?=\s*"(?:[^"]*"[^"]*")*[^"]*$)' file

Exemplo:

$ cat file
first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".
$ grep -oP '"\s*\K[^"]+?(?=\s*"(?:[^"]*"[^"]*")*[^"]*$)' file
One
Two
Three
Four

Outra solução para puxar o cabelo através do verbo PCRE ( *SKIP)(*F) ,

$ grep -oP '[^"]+(?=(?:"[^"]*"[^"]*)*[^"]*$)(*SKIP)(*F)|\s*\K[^"]+(?=\b\s*)' file
One
Two
Three
Four
    
por Avinash Raj 17.11.2014 / 07:22
0

Usando sed :

sed 's/[^"]*"\([^"]\+\)"[^"]*/\n/g' file
[^"]*

O ^ no início de [^"]* ... significa que os caracteres listados na classe de caractere não devem corresponder (somente coincidem com " ). O * significa que " pode ocorrer zero ou mais vezes.

"\([^"]\+\)"

Tudo dentro de \(...\) é um grupo correspondente. O primeiro caractere fora do grupo correspondente é a partida inicial. Uma classe de caractere [^"] está seguindo (Corresponde a todos os caracteres exceto o " ). O quantificador \+ significa que deve haver pelo menos um caractere entre as aspas ( "..." ) em seu arquivo de entrada. Então \) , o final do grupo correspondente. Esse grupo correspondente pode ser acessado pelo índice por meio de .

A última parte [^"]* é igual à primeira parte que corresponde a tudo até o próximo " .

    
por αғsнιη 20.12.2014 / 20:45
0

Uma abordagem alternativa com Python que não requer expressões regulares (embora não seja exatamente robusta), é processar cada linha em seu caractere de arquivo de texto por caractere.

Idéia básica de como isso funciona: se vemos aspas duplas e nenhuma bandeira levantada - levante a bandeira, e se a virmos novamente e a bandeira estiver levantada - abaixe a bandeira. Quando a bandeira é levantada - é assim que sabemos que estamos entre aspas duplas, então podemos armazenar os personagens subseqüentes. Quando a bandeira estiver abaixada, imprima o que lemos.

#!/usr/bin/env python
from __future__ import print_function
import sys

flag=False
quoted_string=[]
for line in sys.stdin:
    for char in line.strip():
        if char == '"':
           if flag:
               flag=False
               if quoted_string:
                  print("".join(quoted_string))
                  quoted_string=[]
           else:
               flag=True
               continue 
        if flag:
           quoted_string.append(char)

E execução de teste:

$ cat input.txt
first matched is "One". the second is here"Two "
and here are in second line" Three ""Four".

$ ./get_quoted_words.py < input.txt                                                                                      
One
Two 
 Three 
Four
    
por Sergiy Kolodyazhnyy 05.02.2017 / 22:36