Cuando una expresión regular contiene grupos, puede haber más de una forma de hacer coincidir una cadena con ella: las expresiones regulares con grupos son ambiguas. Por ejemplo, considere la expresión regular ^.*\([0-9][0-9]*\)$
y la cadena a12
. Hay dos posibilidades:
- Partido
a
contra .*
y 2
contra [0-9]*
; 1
se corresponde con [0-9]
.
- Match
a1
contra .*
y la cadena vacía contra [0-9]*
; 2
se corresponde con [0-9]
.
Sed, como todas las demás herramientas de expresión regular, aplica la primera regla de coincidencia más larga: primero intenta hacer coincidir la primera porción de longitud variable con una cadena que sea lo más larga posible. Si encuentra una manera de hacer coincidir el resto de la cadena con el resto de la expresión regular, está bien. De lo contrario, sed intenta la siguiente coincidencia más larga para la primera porción de longitud variable e intenta nuevamente.
Aquí, la coincidencia con la cadena más larga primero es a1
contra .*
, por lo que el grupo solo coincide 2
. Si desea que el grupo comience antes, algunos motores regexp le permiten hacer los .*
menos codiciosos, pero sed no tiene esa característica. Por lo tanto, debe eliminar la ambigüedad con un ancla adicional. Especifique que el .*
inicio no puede terminar con un dígito, de modo que el primer dígito del grupo es la primera coincidencia posible.
Si el grupo de dígitos no puede estar al principio de la línea:
sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
Si el grupo de dígitos puede estar al comienzo de la línea, y su sed admite el \?
operador para partes opcionales:
sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
Si el grupo de dígitos puede estar al comienzo de la línea, siguiendo las construcciones estándar de expresiones regulares:
sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
Por cierto, es la misma regla de coincidencia más antigua que hace [0-9]*
coincidir los dígitos después del primero, en lugar del siguiente .*
.
Tenga en cuenta que si hay varias secuencias de dígitos en una línea, su programa siempre extraerá la última secuencia de dígitos, nuevamente debido a la regla de coincidencia más larga más antigua aplicada a la inicial .*
. Si desea extraer la primera secuencia de dígitos, debe especificar que lo que viene antes es una secuencia de no dígitos.
sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'
En términos más generales, para extraer la primera coincidencia de una expresión regular, debe calcular la negación de esa expresión regular. Si bien esto es siempre teóricamente posible, el tamaño de la negación crece exponencialmente con el tamaño de la expresión regular que está negando, por lo que esto a menudo no es práctico.
Considere su otro ejemplo:
sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'
Este ejemplo en realidad exhibe el mismo problema, pero no lo ve en las entradas típicas. Si lo alimenta hello CONFIG_FOO_CONFIG_BAR
, el comando anterior se imprime CONFIG_BAR
, no CONFIG_FOO_CONFIG_BAR
.
Hay una manera de imprimir la primera coincidencia con sed, pero es un poco complicado:
sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p
(Suponiendo que sus soportes sed \n
significan una nueva línea en el s
texto de reemplazo). Esto funciona porque sed busca la primera coincidencia de la expresión regular, y no intentamos hacer coincidir lo que precede al CONFIG_…
bit. Como no hay una nueva línea dentro de la línea, podemos usarla como un marcador temporal. El T
comando dice que te rindas si el s
comando anterior no coincide.
Cuando no puedas descubrir cómo hacer algo en sed, recurre a awk. El siguiente comando imprime la primera coincidencia más larga de una expresión regular:
awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'
Y si desea mantenerlo simple, use Perl.
perl -l -ne '/[0-9]+/ && print $&' # first match
perl -l -ne '/^.*([0-9]+)/ && print $1' # last match