Compruebe si una cadena coincide con una expresión regular en la secuencia de comandos Bash

204

Uno de los argumentos que recibe mi guión es una fecha en el siguiente formato: yyyymmdd.

Quiero verificar si obtengo una fecha válida como entrada.

¿Cómo puedo hacer esto? Estoy tratando de usar una expresión regular como:[0-9]\{\8}

Peter Nijem
fuente
Comprobar si el formato es correcto es fácil. Pero no creo que pueda, en bash (con funciones integradas), verificar si la fecha es válida.
RedX

Respuestas:

317

Puede usar la construcción de prueba [[ ]], junto con el operador de coincidencia de expresiones regulares =~, para verificar si una cadena coincide con un patrón de expresiones regulares .

Para su caso específico, puede escribir:

[[ $date =~ ^[0-9]{8}$ ]] && echo "yes"

O más una prueba precisa:

[[ $date =~ ^[0-9]{4}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])$ ]] && echo "yes"
#           |^^^^^^^^ ^^^^^^ ^^^^^^  ^^^^^^ ^^^^^^^^^^ ^^^^^^ |
#           |   |     ^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^^ |
#           |   |          |                   |              |
#           |   |           \                  |              |
#           | --year--   --month--           --day--          |
#           |          either 01...09      either 01..09     end of line
# start of line            or 10,11,12         or 10..29
#                                              or 30, 31

Es decir, puede definir una expresión regular en Bash que coincida con el formato que desee. De esta manera puedes hacer:

[[ $date =~ ^regex$ ]] && echo "matched" || echo "did not match"

donde los comandos posteriores &&se ejecutan si la prueba es exitosa, y los comandos posteriores ||se ejecutan si la prueba no tiene éxito.

Tenga en cuenta que esto se basa en la solución de Aleks-Daniel Jakimenko en la verificación del formato de fecha de entrada del usuario en bash .


En otras conchas puedes usar grep . Si su shell es compatible con POSIX, haga

(echo "$date" | grep -Eq  ^regex$) && echo "matched" || echo "did not match"

En pescado , que no es compatible con POSIX, puede hacer

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"
fedorqui 'así que deja de dañar'
fuente
19
Soy consciente de eso, pero también me gusta tener en cuenta quién pregunta y qué tan lejos están con bash. Si proporcionamos condiciones muy complejas, no aprenderán nada y simplemente regresarán cuando tengan otra duda. Prefiero dar una respuesta más comprensible.
Fedorqui 'SO deja de dañar'
77
Je Bueno, la única forma de aprender es leer mucho código bueno. Si proporciona un código falso que es fácil de entender pero que no se recomienda utilizar, es una mala forma de enseñar. También estoy bastante seguro de que para aquellos que acaban de comenzar a aprender bash (probablemente ya conozcan algunos bits de otro idioma) entenderán la sintaxis bash para expresiones regulares más fácilmente que algunos grepcomandos con -Eflag.
Aleks-Daniel Jakimenko-A.
8
@ Aleks-DanielJakimenko Revisé esta publicación nuevamente y ahora estoy de acuerdo en que es mejor usar bash regex. Gracias por apuntar en la buena dirección, respuesta actualizada.
Fedorqui 'SO deja de dañar'
44
Voto a favor, que permite usarlo un poco más allá de la pregunta OP, para sh, por ejemplo ...
Dereckson
3
@ Aleks-DanielJakimenko usando grep parece ser la mejor opción si usted está utilizando sh, fishu otros proyectiles menos equipados.
tomekwi
47

En bash versión 3 puedes usar el operador '= ~':

if [[ "$date" =~ ^[0-9]{8}$ ]]; then
    echo "Valid date"
else
    echo "Invalid date"
fi

Referencia: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

NOTA: La cita en el operador coincidente dentro de los corchetes dobles, [[]], ya no es necesaria a partir de la versión 3.2 de Bash

aliasav
fuente
20
No deberías usar char "en expresión regular? Porque cuando uso expresion no funciona
Dawid Drozd
Además, el escape de barra diagonal inversa de {y} es igualmente problemático.
kbulgrien
32

Una buena manera de probar si una cadena es una fecha correcta es usar el comando date:

if date -d "${DATE}" >/dev/null 2>&1
then
  # do what you need to do with your date
else
  echo "${DATE} incorrect date" >&2
  exit 1
fi

del comentario: se puede usar el formato

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 
Django Janny
fuente
9
Califique su respuesta, ya que permite que la función de fecha se ocupe de las fechas y no de las expresiones regulares propensas a errores '
Ali
Esto es bueno para verificar las opciones de fecha amplia, pero si necesita verificar un formato de fecha específico, ¿puede hacerlo? Por ejemplo, si lo hago date -d 2017-11-14e, devuelve el martes 14 de noviembre a las 05:00:00 UTC de 2017, pero eso rompería mi script.
Josiah
1
Podría usar algo así: if ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] Comprueba si la fecha es correcta y verifique si el resultado es el mismo que sus datos ingresados. Por cierto, tenga mucho cuidado con el formato de fecha localizado (Mes-Día-Año vs. Día-Mes-Año, por ejemplo)
Django Janny
1
Podría no funcionar, dependiendo de su localidad. Las fechas con formato estadounidense que usan MM-DD-AAAA no funcionarán en ningún otro lugar del mundo, usando DD-MM-AAAA (Europa) o AAAA-MM-DD (algunos lugares en Asia)
Paul
@Paul, ¿qué puede no funcionar? Como está escrito en un comentario, uno puede usar las opciones de formato ...
Betlista
4

Yo usaría en expr matchlugar de =~:

expr match "$date" "[0-9]\{8\}" >/dev/null && echo yes

Esto es mejor que la respuesta actualmente aceptada de usar =~porque =~también coincidirá con cadenas vacías, lo que en mi humilde opinión no debería. Supongamos badvarque no está definido, luego [[ "1234" =~ "$badvar" ]]; echo $?da (incorrectamente) 0, mientras expr match "1234" "$badvar" >/dev/null ; echo $?da el resultado correcto 1.

Tenemos que usar >/dev/nullpara ocultar expr matchel valor de salida , que es el número de caracteres coincidentes o 0 si no se encuentra ninguna coincidencia. Tenga en cuenta que su valor de salida es diferente de su estado de salida . El estado de salida es 0 si se encuentra una coincidencia, o 1 en caso contrario.

En general, la sintaxis para expres:

expr match "$string" "$lead"

O:

expr "$string" : "$lead"

donde $leades una expresión regular Su exit statusserá cierto (0) si leadcoincide con el líder rebanada de string(¿Hay un nombre para esto?). Por ejemplo , expr match "abcdefghi" "abc"salidas true, pero expr match "abcdefghi" "bcd"salidas false. (Gracias a @Carlo Wood por señalar esto.

Penghe Geng
fuente
77
=~no coincide con cadenas vacías, está haciendo coincidir una cadena con un patrón vacío en el ejemplo que da La sintaxis es string =~ pattern, y un patrón vacío coincide con todo.
bstpierre
2
Esto no coincide con una subcadena, devuelve (a la salida estándar) el número de los principales personajes que hacía juego y el estado de salida es verdadera si y sólo si fue emparejado al menos 1 carácter. Es por eso que una cadena vacía (que coincide con 0 caracteres) tiene un estado de salida de falso. Por ejemplo expr match "abcdefghi" "^" && echo Matched || echo No match, y expr match "abcdefghi" "bcd" && echo Matched || echo No match- ambos regresan "0\nNo match". Donde como coincidencia "a.*f"volverá "6\nMatched". Por lo tanto, el uso de '^' en su ejemplo también es innecesario y ya está implícito.
Carlo Wood
@bstpierre: el punto aquí no es si uno puede racionalizar el comportamiento de =~combinar cadenas vacías. Es que este comportamiento puede ser inesperado y puede causar errores. Escribí esta respuesta específicamente porque me quemé.
Penghe Geng
@PengheGeng ¿Comportamiento inesperado? Si un patrón no tiene definición o restricciones, de hecho coincide con cualquier cosa. La ausencia de un patrón coincide con todo. Escribir un código robusto es la respuesta, no justifica una explicación pobre.
Anthony Rutledge
El "código robusto" de @AnthonyRutledge exige el mejor uso de las herramientas disponibles para evitar errores de codificación accidentales. En el código de Shell donde una variable vacía se puede introducir de manera fácil y accidental en cualquier momento por medio de errores ortográficos, no creo que permitir que las variables vacías coincidan es una característica sólida. Aparentemente, el autor de GNU exprestá de acuerdo conmigo.
Penghe Geng
0

Cuando el uso de una expresión regular puede ser útil para determinar si la secuencia de caracteres de una fecha es correcta, no se puede usar fácilmente para determinar si la fecha es válida. Los siguientes ejemplos pasarán la expresión regular, pero son todas las fechas inválidas: 20180231, 20190229, 20190431

Entonces, si desea validar si su cadena de fecha (llamémosla datestr) está en el formato correcto, es mejor analizarla datey solicitar dateconvertir la cadena al formato correcto. Si ambas cadenas son idénticas, tiene un formato válido y una fecha válida.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
kvantour
fuente