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.*
e2
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