Extraindo um regex correspondido com 'sed' sem imprimir os caracteres adjacentes

24

Para todos os médicos "sed" por aí:

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

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

Eu tentei usar o recurso de referência anterior, como abaixo

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

isso funciona para algumas expressões como

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

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

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

MAS o acima se desdobra para algo como

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

isso sempre retorna um dígito como

                 7
                 9
                 .
                 .  
                 6

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

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

P.S .: Eu ficaria grato ao feedback sobre como isso é alcançado em 'sed'.       Eu sei como fazer isso com 'grep' e 'awk'       Eu gostaria de descobrir se o meu - embora limitado - entendimento       'sed' tem buracos e se há maneira de fazer isso em 'sed' que eu       simplesmente ignorado.

    
por darbehdar 12.02.2012 / 15:26

3 respostas

22

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

  • Corresponde a ao .* e 2 ao [0-9]* ; 1 é correspondido por [0-9] .
  • Corresponde a1 a .* e a string vazia a [0-9]* ; 2 é correspondido por [0-9] .

Sed, como todas as outras ferramentas de expressão regular, aplica a regra de correspondência mais longa mais antiga: primeiro tenta corresponder a primeira porção de tamanho variável a uma string que seja o maior tempo possível. Se encontrar uma maneira de combinar o restante da string com o resto do 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 correspondência com a string mais longa primeiro é a1 contra .* , portanto, o grupo corresponde apenas a 2 . Se você quiser que o grupo comece mais cedo, alguns mecanismos de expressão regular permitem deixar o .* menos ganancioso, mas o sed não tem esse recurso. Então você precisa remover a ambigüidade com alguma âncora adicional. Especifique que o .* inicial 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 começo da linha:

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

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

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

A propósito, é a mesma regra de correspondência mais longa mais antiga que faz com que [0-9]* corresponda aos dígitos após o primeiro, em vez do .* subseqüente.

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

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

Geralmente, para extrair a primeira correspondência de um regexp, é necessário calcular a negação desse regexp. Embora isso sempre seja teoricamente possível, o tamanho da negação cresce exponencialmente com o tamanho do regexp que você está negando, portanto, isso geralmente é impraticável.

Considere o seu outro exemplo:

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

Este exemplo realmente exibe o mesmo problema, mas você não o vê em entradas típicas. Se você alimentar hello CONFIG_FOO_CONFIG_BAR , o comando acima imprimirá CONFIG_BAR , não CONFIG_FOO_CONFIG_BAR .

Existe uma maneira de imprimir o primeiro jogo com sed, mas é um pouco complicado:

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

(Assumindo que seu sed suporta \n para significar uma nova linha no texto de substituição de s .) Isso funciona porque o sed procura a correspondência mais antiga do regexp e não tentamos corresponder ao que precede o CONFIG_… pouco. Como não há nova linha dentro da linha, podemos usá-la como marcador temporário. O comando T diz para desistir se o comando s anterior não corresponder.

Quando você não consegue descobrir como fazer algo em sed, vire-se para awk. O comando a seguir imprime a correspondência mais antiga mais antiga de um regexp:

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

E se você quiser mantê-lo simples, use Perl.

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

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

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

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

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

$ grep -Eo '[0-9]+' foo
    
por 24.06.2014 / 14:46
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 do n s no campo de substituição à direita. E, a propósito, a coisa .*CONFIG só funcionaria se houvesse apenas uma correspondência na linha - caso contrário, sempre obteria apenas a última.

Você pode ver este para obter uma descrição de como funciona, mas isso só será impresso em uma linha separada a partida quantas vezes ela 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ê quisesse imprimir a correspondência CONFIG somente se fosse a terceira em uma linha:

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

... embora isso pressuponha que as strings CONFIG são separadas por pelo menos um caractere não alfanumérico para cada ocorrência.

Suponho que, para o número, isso também funcione:

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

... com a mesma advertência de antes sobre o \n do lado direito. Este seria até mais rápido que o primeiro, mas não pode ser aplicado como geralmente, obviamente.

Para o item CONFIG você poderia usar o P;...;D loop acima com seu padrão, ou você poderia fazer:

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

... o que é um pouco mais complicado e funciona ao ordenar corretamente a prioridade de referência de sed . 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 - que cada correspondência CONFIG seja separada por pelo menos um caractere não alfanumérico. Com o GNU sed , você pode escrevê-lo:

sed -En 's/[^C]*(CONFIG\w*)?C?/\n/g;s/(\n)*//g;/C/s/.$//p'
    
por 08.02.2015 / 00:10

Tags