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

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

Estoy aprendiendo Linux y tengo un desafío que parece no poder resolver por mi cuenta. Aquí lo tienes:

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.

Respuesta1

Hay dos formas de interpretar esta pregunta; Me ocuparé de 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 ninguna secuencia de dígitos más larga,o
  2. que contiene una secuencia de cuatro dígitos pero ya no una secuencia de dígitos (ni siquiera por separado).

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


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

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

Esto usaExpresiones regulares en Perl, que Ubuntugrep(grupo GNU) admite a través de -P. No coincidirá con texto como 12345, ni coincidirá con el 1234o 2345que forman parte de él.Pero 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}partidosx4 veces. ( La sintaxis no es específica de las expresiones regulares de Perl; también { }se encuentra en las expresiones regulares extendidas a través de ). Entonces es lo mismo que .grep -E\d{4}\d\d\d\d
  • (?<!\d)es una afirmación retrospectiva negativa de ancho cero. Significa "a menos que esté precedido por \d".
  • (?!\d)es una aserción anticipada negativa de ancho cero. Significa "a menos que vaya seguido de \d".

(?<!\d)y (?!\d)no haga coincidir 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 de dígitos más larga.

Usar solo la búsqueda hacia atrás o 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.

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

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

Por defectoen Ubuntu, cada usuario tiene alias grep='grep --color=auto'en su~.bashrcarchivo. Por lo tanto, obtienes resaltado de color automáticamente cuando ejecutas un comando simple que comienza con grep(aquí es cuandoaliasse expanden) ysalida estándaresuna terminal(esto es lo que--color=autocomprueba). Las coincidencias suelen estar resaltadas en un tono rojo (cerca delbermellón), pero lo he mostrado en negrita y cursiva.Aquí hay una captura de pantalla:
Captura de pantalla que muestra el comando grep, con 12345abc789d0123e4 como salida, con 0123 resaltado en rojo.

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

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

Forma alternativa,SinAfirmaciones de mirar hacia atrás y mirar hacia adelante

Sin embargo, si usted:

  1. necesita un comando que también se ejecute en sistemas que grepno admiten -Po no desean utilizar una expresión regular de Perl,y
  2. no es necesario hacer coincidir los cuatro dígitos específicamente, lo que suele ser el caso si su objetivo es simplemente mostrar líneas que contienen coincidencias,y
  3. están de acuerdo con una solución que es un poco menos elegante

...entonces puedes lograr esto con unexpresión regular extendidaen cambio:

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

Esto coincide con cuatro dígitos y el carácter que no es un dígito (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 [ ]corchetes, coincide con el comienzo de una línea. De manera similar, $coincide con el final de una línea.
  • |mediooy los paréntesis son para agrupar (como en álgebra). Entonces (^|[^0-9])coincide con el comienzo de la línea o con un carácter que no es un dígito, mientras que ($|[^0-9])coincide con el final de la línea o con un carácter que no es un dígito.

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

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

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

Por lo tanto, incluso si sabes cómo hacerlo con un solo patrón, te sugiero usar algo comomattSegunda sugerencia, greprealizar los dos patrones por separado.

Al hacer esto, 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 abreviación dela solución de mattusando \d(y llaves) en lugar de [0-9]:

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

Dado que utiliza [0-9],el camino de Mattes más portátil: funcionará en sistemas que grepno admitan expresiones regulares de Perl. Si usa [0-9](o [[:digit:]]) en lugar de \d, pero continúa usando { }, obtendrá la portabilidad de Matt de manera un poco más concisa:

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

Manera alternativa, con un solo patrón

Si realmente prefieres un grepcomando que

  1. utiliza una sola expresión regular(no dos greps separadas por unatubo, como anteriormente)
  2. para mostrar líneas que contengan al menos una secuencia de cuatro dígitos,
  3. pero no 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 esto no te importe)

...entonces puedes usar:

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

La -xbandera hace que grepse muestren solo las líneas donde coincide toda la línea (en lugar de cualquier líneaque contieneun partido).

He usado una expresión regular de Perl porque creo que la brevedad \daumenta \Dsustancialmente la claridad en este caso. Pero si necesita algo portátil para sistemas que grepno son compatibles -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 uno que no sea un dígito. Cero dígitos (es decir, nada) es una posibilidad para "no más de cuatro dígitos". Esto coincide(a)la cadena vacía o(b)cualquier cuerdafinalizandoen un formato que no sea un dígito y que no contenga secuencias de más de cuatro dígitos.

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

  • A la derecha, (\D\d{0,4})*o ([^0-9][0-9]{0,4})*coincide con cero o más ( *) instancias de un elemento que no sea un 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(a)la cadena vacía o(b)cualquier cuerdacomienzoen un formato que no sea un dígito y que no contenga secuencias de más de cuatro dígitos.

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

Esto garantiza que haya una secuencia de cuatro dígitos en alguna parte y que no haya ninguna secuencia de cinco o más dígitos en ninguna parte.

No es malo ni incorrecto 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), como se sugirió anteriormente y engrep -P '\d{4}' file | grep -Pv '\d{5}'la respuesta de matt.

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).

Respuesta2

Esto te 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 está al 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]

Respuesta3

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

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

También puedes consultareste tutorialpara más usos del comando grep.

Respuesta4

Si grepno admite expresiones regulares de Perl ( -P), utilice 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 tienes dígitos largos y no quieres repetir el patrón (simplemente reemplázalo 4con el número de dígitos que deseas buscar).

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

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

Usar $()es básicamente unsustitución de comando. Mira estocorreopara ver cómo printfse repite el patrón.

información relacionada