Extraer una expresión regular combinada con 'sed' sin imprimir los caracteres circundantes


24

A todos los médicos 'sed' que hay:

¿Cómo puede obtener 'sed' para extraer una expresión regular que ha coincidido en una línea?

En otras palabras, quiero solo la cadena correspondiente a la expresión regular con todos los caracteres que no coinciden de la línea que contiene eliminada.

Intenté usar la función de referencia inversa como se muestra a continuación

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

esto funciona para algunas expresiones como

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

que extrae perfectamente todos los nombres de macro que comienzan con 'CONFIG_ ....' (que se encuentra en algún archivo '* .h') y los imprime todos línea por línea

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

PERO lo anterior se descompone por algo como

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

esto siempre devuelve dígitos individuales como

                 7
                 9
                 .
                 .  
                 6

en lugar de extraer un campo de número contiguo como.

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

PD: Agradecería recibir comentarios sobre cómo se logra esto en 'sed'. Sé cómo hacer esto con 'grep' y 'awk'. Me gustaría saber si mi comprensión, aunque limitada, de 'sed' tiene agujeros y si hay alguna manera de hacerlo en 'sed' que
tengo. simplemente pasado por alto.

Respuestas:


22

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 acontra .*y 2contra [0-9]*; 1se corresponde con [0-9].
  • Match a1contra .*y la cadena vacía contra [0-9]*; 2se 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 a1contra .*, 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 \nsignifican una nueva línea en el stexto 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 Tcomando dice que te rindas si el scomando 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

22

Si bien no sed, una de las cosas que a menudo se pasan por alto es grep -oque, en mi opinión, es la mejor herramienta para esta tarea.

Por ejemplo, si desea obtener todos los CONFIG_parámetros de una configuración del núcleo, usaría:

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

Si desea obtener secuencias contiguas de números:

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

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

... hará esto sin problemas, aunque es posible que necesite nuevas líneas literales en lugar de la ns en el campo de sustitución de la derecha. Y, por cierto, la .*CONFIGcosa solo funcionaría si solo hubiera una coincidencia en la línea; de lo contrario, siempre obtendría solo la última.

Puede ver esto para obtener una descripción de cómo funciona, pero esto imprimirá en una línea separada solo la coincidencia tantas veces como ocurra en una línea.

Puede usar la misma estrategia para obtener la [num]th ocurrencia en una línea. Por ejemplo, si desea imprimir la coincidencia CONFIG solo si fue la tercera en una línea:

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

... aunque eso supone que las CONFIGcadenas están separadas por al menos un carácter no alfanumérico para cada aparición.

Supongo que, para el número, esto también funcionaría:

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

... con la misma advertencia que antes sobre la mano derecha \n. Este incluso sería más rápido que el primero, pero obviamente no puede aplicarse como generalmente.

Para lo de CONFIG, puede usar el P;...;Dbucle anterior con su patrón, o puede hacer:

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

... que es un poco más complicado y funciona ordenando correctamente sedla prioridad de referencia. También aísla todas las coincidencias de CONFIG en una línea de una vez, aunque asume lo mismo que antes, aunque cada coincidencia de CONFIG estará separada por al menos un carácter no alfanumérico. Con GNU sedpodrías escribirlo:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.