Encontrar texto entre dos caracteres o cadenas específicos

17

Digamos que tengo líneas como esta:

*[234]*
*[23]*
*[1453]*

donde *representa cualquier cadena (excepto una cadena del formulario [number]). ¿Cómo puedo analizar estas líneas con una utilidad de línea de comandos y extraer el número entre paréntesis?

De manera más general, ¿cuál de estas herramientas cut, sed, grepo awksería apropiado para tal tarea?

Amelio Vazquez-Reina
fuente

Respuestas:

16

Si tiene GNU grep, puede usar su -oopción para buscar una expresión regular y generar solo la parte correspondiente. (Otras implementaciones grep solo pueden mostrar la línea completa). Si hay varias coincidencias en una línea, se imprimen en líneas separadas.

grep -o '\[[0-9]*\]'

Si solo quieres los dígitos y no los corchetes, es un poco más difícil; debe usar una aserción de ancho cero: una expresión regular que coincida con la cadena vacía, pero solo si está precedida o seguida, según sea el caso, por un corchete. Las aserciones de ancho cero solo están disponibles en la sintaxis de Perl.

grep -P -o '(?<=\[)[0-9]*(?=\])'

Con sed, debe desactivar la impresión -ny hacer coincidir toda la línea y conservar solo la parte correspondiente. Si hay varias coincidencias posibles en una línea, solo se imprime la última coincidencia. Consulte Extracción de una expresión regular combinada con 'sed' sin imprimir los caracteres circundantes para obtener más detalles sobre el uso de sed aquí.

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

o si solo quieres los dígitos y no los corchetes:

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

Sin grep -o, Perl es la herramienta de elección aquí si quieres algo que sea simple y comprensible. En cada línea ( -n), si la línea contiene una coincidencia para \[[0-9]*\], imprima esa coincidencia ( $&) y una nueva línea ( -l).

perl -l -ne '/\[[0-9]*\]/ and print $&'

Si solo desea los dígitos, coloque paréntesis en la expresión regular para delimitar un grupo e imprima solo ese grupo.

perl -l -ne '/\[([0-9]*)\]/ and print $1'

PD Si solo desea requerir uno o más dígitos entre los corchetes, cambie [0-9]*a [0-9][0-9]*, o a [0-9]+en Perl.

Gilles 'SO- deja de ser malvado'
fuente
Todo bien, aparte de eso, quiere "extraer el número entre paréntesis". Creo que "excepto [number]" significa excepto[0-9]
Peter.O
1
@ Peter.O Entendí que "excepto [número]" significa que no hay otras partes de la línea de esa forma. Pero edité mi respuesta para mostrar cómo imprimir solo los dígitos, por si acaso.
Gilles 'SO- deja de ser malvado'
1
¡Esas perlafirmaciones de expresiones regulares se ven realmente útiles! He estado leyendo sobre ellos después de verte usar afirmaciones tanto hacia atrás como hacia adelante, incluso en grep (había desactivado el hecho de que puedes elegir un motor de expresiones regulares). A partir de ahora dedicaré un poco más de tiempo a la expresión regular de Perl. Gracias ... PD ... Acabo de leer man grep... "Esto es muy experimental y grep -P puede advertir sobre características no implementadas". ... espero que eso no signifique inestable (?) ...
Peter.O
5

No puedes hacerlo con cut.

  1. tr -c -d '0123456789\012'
  2. sed 's/[^0-9]*//g'
  3. awk -F'[^0-9]+' '{ print $1$2$3 }'
  4. grep -o -E '[0-9]+'

tr es el ajuste más natural para el problema y probablemente se ejecute más rápido, pero creo que necesitaría entradas gigantescas para separar cualquiera de estas opciones en términos de velocidad.

Kyle Jones
fuente
Para sed, ^.*es codicioso y consume todo menos el último dígito, y +necesita ser \+o usar el posix \([0-9][0-9]*\)... y, en cualquier caso, 's/[^0-9]*//g'funciona igual de bien, ... Thanks for the ejemplo tr -c`, pero ¿no es ese \012rastro superfluo?
Peter.O
@ Peter Gracias por atrapar eso. Hubiera jurado que probé el ejemplo sed. :( Lo he cambiado a su versión. En cuanto a \012: es necesario, de lo contrario trse comerá las nuevas líneas.
Kyle Jones
Aha ... que estaba viendo como \0, 1, 2(o incluso \, 0, 1, 2). Parece que no estoy lo suficientemente en sintonía con el octal. Gracias.
Peter.O
4

Si se refiere a extraer un conjunto de dígitos consecutivos entre caracteres no dígitos, supongo sed, y awkson la mejor (aunque greptambién es capaz de darle los caracteres coincidentes):

sed: por supuesto, puede hacer coincidir los dígitos, pero quizás sea interesante hacer lo contrario, eliminar los no dígitos (funciona siempre que solo haya un número por línea):

$ echo nn3334nn | sed -e 's/[^[[:digit:]]]*//g'
3344

grep: puede coincidir dígitos consecutivos

$ echo nn3334nn | grep -o '[[:digit:]]*'
3344

No doy un ejemplo awkporque tengo experiencia nula con él; Es interesante notar que, aunque sedes un cuchillo suizo, greple brinda una forma más simple y legible de hacerlo, que también funciona para más de un número en cada línea de entrada (la -oúnica imprime las partes coincidentes de la entrada, cada una) en su propia línea):

$ echo dna42dna54dna | grep -o '[[:digit:]]*'
42
54
njsg
fuente
Del mismo modo que una comparación, aquí es un sedeqivalent del "más de un número por línea" ejemplo grep -o '[[:digit:]]*'. . . sed -nr '/[0-9]/{ s/^[^[0-9]*|[^0-9]*$//g; s/[^0-9]+/\n/g; p}'... (+1)
Peter.O
2

Como se ha dicho que no se puede hacer esto cut, demostraré que es fácilmente posible producir una solución que al menos no sea peor que algunas de las otras, a pesar de que no apruebo el uso cutcomo "el mejor" (o incluso una solución particularmente buena). Debería decirse que cualquier solución que no busque específicamente *[y ]*alrededor de los dígitos hace suposiciones simplificadoras y, por lo tanto, es propenso a fallar en los ejemplos más complejos que el dado por el autor de la pregunta (por ejemplo, dígitos fuera *[y ]*, que no deberían mostrarse). Esta solución verifica al menos los corchetes, y podría extenderse para verificar también los asteriscos (se deja como ejercicio para el lector):

cut -f 2 -d '[' myfile.txt | cut -f 1 -d ']'

Esto hace uso de la -dopción, que especifica un delimitador. Obviamente, también puede canalizar la cutexpresión en lugar de leer desde un archivo. Si bien cutes probable que sea bastante rápido, ya que es simple (sin motor regex), debe invocarlo al menos dos veces (o un poco más de tiempo para verificarlo *), lo que crea una sobrecarga del proceso. La única ventaja real de esta solución es que es bastante legible, especialmente para usuarios ocasionales que no conocen bien las construcciones de expresiones regulares.

Thomas
fuente