A correspondência de grupos com o grep inclui caracteres extras

3

Eu queria extrair algum texto com regex no bash, então decidi experimentar o seguinte exemplo simples.

echo "abc def ghi" | grep -Po " \K(.*?) "

Eu esperava obter um "def" , mas para minha surpresa um "def " (com um espaço extra final) foi o que consegui.

Estou interessado em entender por que grep também inclui o espaço extra no final e como se livrar dele. Eu sei que eu poderia pós-processar o resultado com outra linha, mas estou interessado em resolver isso com o grep.

    
por devoured elysium 18.01.2015 / 08:23

2 respostas

5

Resumindo:

\K

faz com que o grep mantenha tudo anterior ao \ K e não o inclua na correspondência. Isso não afeta o que vem após o \K() .

Isso pode ser o suficiente:

" \K(.+)(?= )"

Em que (?= ) é um grupo sem captura.

ou melhor:

" \K([^ ]+)(?= )"
" \K(\w+)(?= )"

ou similar.

    
por 18.01.2015 / 08:36
2

Um BRE fazendo o que você está tentando em sed pode parecer:

sed 's/ *\(\([^ ]*\) *\)\{[num]\}.*//'

... ou como um ERE para os sed s que o suportam, como as versões GNU e BSD:

sed -E 's/ *(([^ ]*) *){[num]}.*//p'

... ou a expressão começará sua correspondência no primeiro caractere do grupo [num] th (onde [num] é um inteiro positivo) de [^ ]* caracteres não-espaciais no padrão -space e continue correspondendo até o final da linha.

O importante, no entanto, é que ele faz subgrupos de algumas correspondências:

  • (([^ ]*) *){[num]} - este grupo corresponde a tantas ocorrências de [num] de grupos não espaciais e de qualquer caractere de espaço subseqüente e pode ser referência de volta como .
    • {[num]} - quando um padrão é correspondido \{[num]\} vezes a única referência a ele que permanece é a última - e assim, mesmo que esse grupo corresponda a tantas ocorrências do padrão como especificado, a única referência retornada será a última.
  • ([^ ]*) - o subgrupo do grupo acima, no entanto, corresponde apenas ao subconjunto de caracteres não espaciais correspondidos em . Este subgrupo pode ser referenciado em .
  • * e .* - corresponde a qualquer / todos os caracteres de espaço que levam ao espaço padrão e a qualquer / todos os caracteres após as ocorrências correspondidas nas subexpressões.
  • // - isso substitui todos os itens acima apenas pelo grupo referenciado em .

Como [^ ]* e * são complementos booleanos e que [^ ]* U * juntos podem descrever qualquer sequência possível, a regex acima funciona universalmente.

Para o seu exemplo:

for n in 1 2 3 4
do  echo "abc def ghi" | 
    sed -E "s/ *(([^ ]*) *){$n}.*//"
done | sed -n l

... imprime ...

abc$
def$
ghi$
$

Como é, ele sempre imprimirá uma linha em branco para uma ocorrência especificada acima da requisitada, mas - se isso não for desejável - a linha pode ser removida da saída inteiramente como:

sed -En 's/ *(([^ ]*) *){[num]}.*//;/./p'

Levando isso um pouco mais longe, a substituição pode ser aplicada globalmente para obter somente cada [num] th ocorrência. E como * é bastante limitante, eu farei com [[:space:]]* - o que corresponderá a qualquer <space><tab><newline><vertical tab><return> .

s=
{   printf "${s:=$(printf '\r\v\t%10s')}"
    seq -s"$s" 100
} | sed -En "s/[${s:=[:space:]}]*(([^$s]*)[$s]*){21}/\
/g;      /[^$s]/s/\n*$//p"

Antes de aplicar sed a ele, o printf ...; seq ... acima imprime uma única linha como:

\r\v\t          1\r\v\t          2\r\v\t          3\r\v\t...

... e assim por diante. Mas aplicar o sed acima a ele é:

21
42
63
84

... e nenhum espaço em branco segue os números impressos.

    
por 18.01.2015 / 23:55