¿Cómo grep para grupos de n dígitos, pero no más de n?

33

Estoy aprendiendo Linux, y tengo un desafío que parece que no puedo resolver por mi cuenta. Aquí está:

grep una línea de un archivo que contiene 4 números seguidos pero no más de 4.

No estoy seguro de cómo abordar esto. Puedo buscar números específicos pero no su cantidad en una cadena.

Buda
fuente
2
¿Debería 1234a12345mostrarse una línea como , o no?
Eliah Kagan
@Buddha necesitas explicar tu pregunta junto con un ejemplo.
Avinash Raj
si los números están precedidos por un espacio o inicio del ancla de línea y seguidos por un espacio o un final del ancla de línea, entonces simplemente puede usar límites de palabras. \b\d{4}\b
Avinash Raj
1
Esta pregunta difiere de algunas preguntas sobre expresiones regulares al ser explícitamente sobre el uso de grep . Las preguntas sobre el uso de las utilidades de Unix en Ubuntu, como grep, sed y awk, siempre se han considerado correctas aquí. A veces las personas preguntan cómo hacer un trabajo con la herramienta incorrecta ; entonces la falta de contexto es un gran problema, pero eso no es lo que está sucediendo aquí. Esto es sobre el tema, lo suficientemente claro como para ser respondido de manera útil, útil para nuestra comunidad, y no hay ningún beneficio en evitar nuevas respuestas o impulsarlo hacia la eliminación o la migración. Estoy votando para reabrirlo.
Eliah Kagan
1
Muchas gracias chicos, no tenía idea de que recibiría tanta información. Esta es la respuesta que estaba buscando: grep -E '(^ ​​| [^ 0-9]) [0-9] {4} ($ | [^ 0-9])' archivo. El comando debe poder tirar de una cadena como esta (que lo hace): abc1234abcd99999
Buda

Respuestas:

52

Hay dos formas de interpretar esta pregunta; Abordaré ambos casos. Es posible que desee mostrar líneas:

  1. que contienen una secuencia de cuatro dígitos que en sí misma no forma parte de una secuencia de dígitos más larga, o
  2. que contiene una secuencia de cuatro dígitos pero ya no es una secuencia de dígitos (ni siquiera por separado).

Por ejemplo, (1) se mostraría 1234a56789, pero (2) no.


Si desea mostrar todas las líneas que contienen una secuencia de cuatro dígitos que no es parte de una secuencia de dígitos más larga, una forma es:

grep -P '(?<!\d)\d{4}(?!\d)' file

Esto utiliza expresiones regulares de Perl , que son compatibles con Ubuntu grep( GNU grep ) -P. No coincidirá con el texto como 12345, ni coincidirá con el 1234o 2345que son parte de él. Pero va a coincidir con el 1234de 1234a56789.

En expresiones regulares de Perl:

  • \dsignifica cualquier dígito (es una forma corta de decir [0-9]o [[:digit:]]).
  • x{4}coincide x4 veces. (la { }sintaxis no es específica de las expresiones regulares de Perl; también está en expresiones regulares extendidas a través de grep -E). Así \d{4}es lo mismo que \d\d\d\d.
  • (?<!\d)es una afirmación negativa de ancho cero. Significa "a menos que sea precedido por \d".
  • (?!\d)es una afirmación negativa anticipada de ancho cero. Significa "a menos que sea seguido por \d".

(?<!\d)y (?!\d)no coincide con el texto fuera de la secuencia de cuatro dígitos; en cambio, (cuando se usan juntos) evitarán que una secuencia de cuatro dígitos coincida si es parte de una secuencia más larga de dígitos.

Usar solo el mirar hacia atrás o solo mirar hacia adelante es insuficiente porque la subsecuencia de cuatro dígitos más a la derecha o más a la izquierda aún coincidiría.

Una ventaja de usar las afirmaciones de mirar hacia atrás y mirar hacia adelante es que su patrón solo coincide con las secuencias de cuatro dígitos, y no con el texto circundante. Esto es útil cuando se utiliza el resaltado de color (con la --coloropción).

ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4

Por defecto en Ubuntu, cada usuario tiene alias grep='grep --color=auto'en su ~.bashrcarchivo . Por lo tanto, obtiene el resaltado de color automáticamente cuando ejecuta un comando simple que comienza con grep(esto es cuando se expanden los alias ) y la salida estándar es un terminal (esto es lo que se verifica). Las coincidencias generalmente se resaltan en un tono rojo (cerca de bermellón ), pero lo he mostrado en negrita en cursiva. Aquí hay una captura de pantalla:--color=auto
Captura de pantalla que muestra ese comando grep, con 12345abc789d0123e4 como salida, con el 0123 resaltado en rojo.

E incluso puede grepimprimir solo texto coincidente, y no toda la línea, con -o:

ek@Io:~$ grep -oP '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
0123

De forma alternativa, sin afirmaciones de mirar atrás y mirar atrás

Sin embargo, si usted:

  1. necesita un comando que también se ejecute en sistemas en los grepque no sea compatible -Po no quiera usar una expresión regular de Perl, y
  2. no es necesario que coincida específicamente con los cuatro dígitos, lo cual suele ser el caso si su objetivo es simplemente mostrar líneas que contengan coincidencias, y
  3. están de acuerdo con una solución que es un poco menos elegante

... entonces puede lograr esto con una expresión regular extendida en su lugar:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

Esto coincide con cuatro dígitos y el carácter sin dígitos, o el principio o el final de la línea, que los rodea. Específicamente:

  • [0-9]coincide con cualquier dígito (como [[:digit:]], o \den expresiones regulares de Perl) y {4}significa "cuatro veces". Entonces [0-9]{4}coincide con una secuencia de cuatro dígitos.
  • [^0-9]coincide con caracteres que no están en el rango de 0hasta 9. Es equivalente a [^[:digit:]](o \D, en expresiones regulares de Perl).
  • ^, cuando no aparece entre [ ]paréntesis, coincide con el comienzo de una línea. Del mismo modo, $coincide con el final de una línea.
  • |medios o y paréntesis son para agrupar (como en álgebra). Por lo tanto, (^|[^0-9])coincide con el comienzo de la línea o un carácter sin dígitos, mientras que ($|[^0-9])coincide con el final de la línea o con un carácter sin dígitos.

Entonces, las coincidencias ocurren solo en líneas que contienen una secuencia de cuatro dígitos ( [0-9]{4}) que es simultáneamente:

  • al comienzo de la línea o precedido por un no dígito ( (^|[^0-9])), y
  • al final de la línea o seguido de un no dígito ( ($|[^0-9])).

Si, por otro lado, desea mostrar todas las líneas que contienen una secuencia de cuatro dígitos, pero no contiene ninguna secuencia de más de cuatro dígitos (incluso una que esté separada de otra secuencia de solo cuatro dígitos), entonces conceptualmente su El objetivo es encontrar líneas que coincidan con un patrón pero no con otro.

Por lo tanto, incluso si sabe cómo hacerlo con un solo patrón, le sugiero que use algo como la segunda sugerencia de grepMatt , para los dos patrones por separado.

Al hacerlo, no se beneficia mucho de ninguna de las características avanzadas de las expresiones regulares de Perl, por lo que es posible que prefiera no usarlas. Pero de acuerdo con el estilo anterior, aquí hay una reducción de la solución de Matt usando \d(y llaves) en lugar de [0-9]:

grep -P '\d{4}' file | grep -Pv '\d{5}'

Como utiliza [0-9], Matt's es más portátil: funcionará en sistemas donde grepno admite expresiones regulares de Perl. Si usa [0-9](o [[:digit:]]) en lugar de \d, pero continúa usándolo { }, obtendrá la portabilidad de Matt's de manera un poco más concisa:

grep -E '[0-9]{4}' file | grep -Ev '[0-9]{5}'

De forma alternativa, con un solo patrón

Si realmente prefieres un grepcomando que

  1. usa una sola expresión regular (no dos greps separadas por una tubería , como se indicó anteriormente)
  2. para mostrar líneas que contienen al menos una secuencia de cuatro dígitos,
  3. pero sin secuencias de cinco (o más) dígitos,
  4. y no te importa hacer coincidir toda la línea, no solo los dígitos (probablemente no te importe esto)

... entonces puedes usar:

grep -Px '(\d{0,4}\D)*\d{4}(\D\d{0,4})*' file

El -xindicador hace que se grepmuestren solo las líneas donde coincide la línea completa (en lugar de cualquier línea que contiene una coincidencia).

He usado una expresión regular de Perl porque creo que la brevedad \dy la \Dclaridad sustancialmente aumentan en este caso. Pero si necesita algo portátil para sistemas donde grepno es compatible -P, puede reemplazarlos con [0-9]y [^0-9](o con [[:digit:]]y [^[:digit]]):

grep -Ex '([0-9]{0,4}[^0-9])*[0-9]{4}([^0-9][0-9]{0,4})*' file

La forma en que funcionan estas expresiones regulares es:

  • En el medio, \d{4}o [0-9]{4}coincide con una secuencia de cuatro dígitos. Es posible que tengamos más de uno de estos, pero necesitamos tener al menos uno.

  • A la izquierda, (\d{0,4}\D)*o ([0-9]{0,4}[^0-9])*coincide con cero o más ( *) instancias de no más de cuatro dígitos seguidos de un no dígito. Cero dígitos (es decir, nada) es una posibilidad para "no más de cuatro dígitos". Esto coincide con (a) la cadena vacía o (b) cualquier cadena que termine en un no dígito y que no contenga ninguna secuencia de más de cuatro dígitos.

    Dado que el texto inmediatamente a la izquierda de la central \d{4}(o [0-9]{4}) debe estar vacío o terminar con un no dígito, esto evita que la central \d{4}coincida con cuatro dígitos que tienen otro (quinto) dígito justo a la izquierda de ellos.

  • A la derecha, (\D\d{0,4})*o ([^0-9][0-9]{0,4})*coincide con cero o más ( *) instancias de un no dígito seguido de no más de cuatro dígitos (que, como antes, podrían ser cuatro, tres, dos, uno o incluso ninguno). Esto coincide con (a) la cadena vacía o (b) cualquier cadena que comience en un no dígito y que no contenga ninguna secuencia de más de cuatro dígitos.

    Dado que el texto inmediatamente a la derecha de la central \d{4}(o [0-9]{4}) debe estar vacío o comenzar con un no dígito, esto evita que la central \d{4}coincida con cuatro dígitos que tienen otro (quinto) dígito justo a la derecha de ellos.

Esto asegura que una secuencia de cuatro dígitos esté presente en alguna parte, y que ninguna secuencia de cinco o más dígitos esté presente en ninguna parte.

No está mal ni está mal hacerlo de esta manera. Pero quizás la razón más importante para considerar esta alternativa es que aclara el beneficio de usar (o similar) en su lugar, como se sugirió anteriormente y en la respuesta de Matt .grep -P '\d{4}' file | grep -Pv '\d{5}'

De esa manera, queda claro que su objetivo es seleccionar líneas que contengan una cosa pero no otra. Además, la sintaxis es más simple (por lo que muchos lectores / mantenedores pueden entenderla más rápidamente).

Eliah Kagan
fuente
9

Esto le mostrará 4 números seguidos pero no más.

grep '[0-9][0-9][0-9][0-9][^0-9]' file

Tenga en cuenta que ^ significa no

Hay un problema con esto, aunque no estoy seguro de cómo solucionarlo ... si el número es el final de la línea, entonces no aparecerá.

Sin embargo, esta versión más fea funcionaría para ese caso

grep '[0-9][0-9][0-9][0-9]' file | grep -v [0-9][0-9][0-9][0-9][0-9]
mate
fuente
oops, no necesitaba ser egrep - lo he editado
mate
2
El primero está mal: encuentra a12345b, porque coincide 2345b.
Volker Siegel
0

Si grepno admite expresiones regulares perl ( -P), use el siguiente comando de shell:

grep -w "$(printf '[0-9]%.0s' {1..4})" file

donde printf '[0-9]%.0s' {1..4}producirá 4 veces [0-9]. Este método es útil cuando tiene dígitos largos y no desea repetir el patrón (simplemente reemplace 4con su número de dígitos para buscar).

El uso -wbuscará las palabras completas. Sin embargo, si está interesado en cadenas alfanuméricas, como 1234a, agregue [^0-9]al final del patrón, por ejemplo

grep "$(printf '[0-9]%.0s' {1..4})[^0-9]" file

Usar $()es básicamente una sustitución de comando . Mira esta publicación para ver cómo se printfrepite el patrón.

kenorb
fuente
0

Puede probar el siguiente comando reemplazándolo filecon el nombre real del archivo en su sistema:

grep -E '(^|[^0-9])[0-9]{4}($|[^0-9])' file

También puede consultar este tutorial para más usos del comando grep.

Mike Tyson
fuente