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
a
contra .*
e 2
contra [0-9]*
; 1
é correspondido por [0-9]
.
- Combine
a1
contra .*
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 é a1
contra .*
; 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 \n
signifique uma nova linha no s
texto 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 T
comando diz para desistir se o s
comando 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