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:
- 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
- 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 1234
o 2345
que forman parte de él.Pero coincidirá con el 1234
de 1234a56789
.
En expresiones regulares de Perl:
\d
significa cualquier dígito (es una forma corta de decir[0-9]
o[[:digit:]]
).x{4}
partidosx
4 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 --color
opción).
ek@Io:~$ grep -P '(?<!\d)\d{4}(?!\d)' <<< 12345abc789d0123e4
12345abc789d0123e4
Por defectoen Ubuntu, cada usuario tiene alias grep='grep --color=auto'
en su~.bashrc
archivo. 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=auto
comprueba). 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:
E incluso puedes grep
imprimir 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:
- necesita un comando que también se ejecute en sistemas que
grep
no admiten-P
o no desean utilizar una expresión regular de Perl,y - 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
- 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\d
en 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 de0
hasta9
. 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, grep
realizar 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 grep
no 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 grep
comando que
- utiliza una sola expresión regular(no dos
grep
s separadas por unatubo, como anteriormente) - para mostrar líneas que contengan al menos una secuencia de cuatro dígitos,
- pero no secuencias de cinco (o más) dígitos,
- 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 -x
bandera hace que grep
se 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 \d
aumenta \D
sustancialmente la claridad en este caso. Pero si necesita algo portátil para sistemas que grep
no 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 file
con 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 grep
no 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 4
con el número de dígitos que deseas buscar).
El uso -w
buscará 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 printf
se repite el patrón.