Extraindo um regex correspondente a 'sed' sem imprimir os caracteres ao redor


24

Para todos os médicos 'sed' lá fora:

Como você pode obter 'sed' para extrair uma expressão regular que corresponda em uma linha?

Em outras palavras, eu quero apenas a string correspondente à expressão regular com todos os caracteres não correspondentes da linha que contenha removidos.

Tentei usar o recurso de referência posterior, como abaixo

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

isso funciona para algumas expressões como

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

que extrai ordenadamente todos os nomes de macro começando com 'CONFIG_ ....' (encontrado em algum arquivo '* .h') e os imprime todos linha por linha

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

MAS o exposto acima se divide em algo como

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

isso sempre retorna dígitos únicos como

                 7
                 9
                 .
                 .  
                 6

em vez de extrair um campo numérico contíguo, como.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

PS: Eu agradeceria o feedback de como isso é alcançado no 'sed'. Eu sei como fazer isso com 'grep' e 'awk'. Gostaria de descobrir se meu entendimento - embora limitado - de 'sed' tem buracos e se há alguma maneira de fazer isso em 'sed', o que eu
tenho simplesmente esquecido.

Respostas:


22

Quando um regexp contém grupos, pode haver mais de uma maneira de corresponder uma string a ele: regexps com grupos são ambíguos. Por exemplo, considere a regexp ^.*\([0-9][0-9]*\)$e a sequência a12. Existem duas possibilidades:

  • Combinar acontra .*e 2contra [0-9]*; 1é correspondido por [0-9].
  • Combine a1contra .*e a sequência vazia contra [0-9]*; 2é correspondido por [0-9].

O Sed, como todas as outras ferramentas de regexp existentes, aplica a regra de correspondência mais longa mais antiga: primeiro tenta combinar a primeira parte de comprimento variável com uma string o maior tempo possível. Se encontrar uma maneira de combinar o restante da string com o restante da regexp, tudo bem. Caso contrário, sed tenta a próxima correspondência mais longa para a primeira parte de comprimento variável e tenta novamente.

Aqui, a partida com a corda mais longa primeiro é a1contra .*; portanto, o grupo apenas combina 2. Se você deseja que o grupo inicie mais cedo, alguns mecanismos de regexp permitem que você torne o .*menos ganancioso, mas o sed não possui esse recurso. Portanto, você precisa remover a ambiguidade com alguma âncora adicional. Especifique que o líder .*não pode terminar com um dígito, para que o primeiro dígito do grupo seja a primeira correspondência possível.

  • Se o grupo de dígitos não puder estar no início da linha:

    sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
    
  • Se o grupo de dígitos puder estar no início da linha e o sed sed oferecer suporte ao \?operador para peças opcionais:

    sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
    
  • Se o grupo de dígitos puder estar no início da linha, seguindo as construções regexp padrão:

    sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
    

A propósito, é a mesma regra de correspondência mais longa mais antiga que faz [0-9]*corresponder os dígitos após o primeiro, e não o subsequente .*.

Observe que, se houver várias seqüências de dígitos em uma linha, seu programa sempre extrairá a última sequência de dígitos, novamente devido à regra de correspondência mais antiga aplicada à inicial .*. Se você deseja extrair a primeira sequência de dígitos, é necessário especificar que o que vem antes é uma sequência de não dígitos.

sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'

De maneira mais geral, para extrair a primeira correspondência de uma regexp, você precisa calcular a negação dessa regexp. Embora isso seja sempre teoricamente possível, o tamanho da negação aumenta exponencialmente com o tamanho da regexp que você está negando, portanto, isso geralmente é impraticável.

Considere seu outro exemplo:

sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'

Este exemplo realmente exibe o mesmo problema, mas você não o vê em entradas típicas. Se você alimentá-lo hello CONFIG_FOO_CONFIG_BAR, o comando acima será impresso CONFIG_BAR, não CONFIG_FOO_CONFIG_BAR.

Existe uma maneira de imprimir a primeira partida com o sed, mas é um pouco complicado:

sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p

(Supondo que o seu sed suporte \nsignifique uma nova linha no stexto de substituição.) Isso funciona porque o sed procura a correspondência mais antiga do regexp e não tentamos corresponder ao que precede a parte CONFIG_…. Como não há nova linha dentro da linha, podemos usá-la como um marcador temporário. O Tcomando diz para desistir se o scomando anterior não corresponder.

Quando você não conseguir descobrir como fazer algo no sed, vire para awk. O comando a seguir imprime a correspondência mais longa mais antiga de uma regexp:

awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'

E se você quiser manter as coisas simples, use Perl.

perl -l -ne '/[0-9]+/ && print $&'       # first match
perl -l -ne '/^.*([0-9]+)/ && print $1'  # last match

22

Embora não seja sed, uma das coisas muitas vezes esquecidas é grep -oque, na minha opinião, é a melhor ferramenta para essa tarefa.

Por exemplo, se você deseja obter todos os CONFIG_parâmetros de uma configuração do kernel, use:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Se você deseja obter seqüências contíguas de números:

$ grep -Eo '[0-9]+' foo

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... fará isso sem qualquer problema, embora você possa precisar de novas linhas literais no lugar de ns no campo de substituição à direita. E, a propósito, a .*CONFIGcoisa só funcionaria se houvesse apenas uma partida na linha - caso contrário, sempre teria apenas a última.

Você pode ver isso para obter uma descrição de como funciona, mas isso imprimirá em uma linha separada apenas a correspondência quantas vezes ocorrer em uma linha.

Você pode usar a mesma estratégia para obter a [num]th ocorrência em uma linha. Por exemplo, se você deseja imprimir a correspondência CONFIG apenas se for a terceira em uma linha:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... embora isso assuma que as CONFIGseqüências de caracteres sejam separadas por pelo menos um caractere não alfanumérico para cada ocorrência.

Suponho - para a coisa número - isso também funcionaria:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... com a mesma ressalva de antes sobre a mão direita \n. Este seria até mais rápido que o primeiro, mas não pode ser aplicado de maneira geral, obviamente.

Para a coisa CONFIG, você pode usar o P;...;Dloop acima com seu padrão, ou você pode fazer:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... que é um pouco mais envolvido e funciona ordenando corretamente seda prioridade de referência. Ele também isola todas as correspondências CONFIG em uma linha de uma só vez - embora faça a mesma suposição de antes - de que cada correspondência CONFIG será separada por pelo menos um caractere não alfanumérico. Com o GNU sedvocê pode escrever:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.