Verifique se uma string corresponde a um regex no script Bash


204

Um dos argumentos que o meu script recebe é uma data no seguinte formato: yyyymmdd.

Quero verificar se recebo uma data válida como entrada.

Como posso fazer isso? Estou tentando usar um regex como:[0-9]\{\8}


Verificar se o formato está correto é fácil. Mas não acho que você possa, no bash (com built-ins), verificar se a data é válida.
RedX 14/01

Respostas:


317

Você pode usar a construção de teste [[ ]], juntamente com o operador de correspondência de expressão regular =~, para verificar se uma sequência corresponde a um padrão de expressão regular .

Para o seu caso específico, você pode escrever:

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

Ou mais, um teste preciso:

[[ $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

Ou seja, você pode definir um regex no Bash que corresponda ao formato desejado. Desta forma, você pode fazer:

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

onde os comandos posteriores &&são executados se o teste for bem-sucedido e os comandos posteriores ||são executados se o teste for malsucedido.

Observe que isso se baseia na solução de Aleks-Daniel Jakimenko na verificação de formato de data de entrada do usuário no bash .


Em outros shells, você pode usar o grep . Se o seu shell for compatível com POSIX, faça

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

No peixe , que não é compatível com POSIX, você pode fazer

echo "$date" | grep -Eq "^regex\$"; and echo "matched"; or echo "did not match"

19
Estou ciente disso, mas também gosto de levar em consideração quem está perguntando e até que ponto eles estão com o bash. Se fornecermos condições muito complexas, elas não aprenderão nada e só voltarão quando tiverem outra dúvida. Prefiro dar uma resposta mais compreensível.
fedorqui 'Então pare de prejudicar'

7
Heh. Bem, a única maneira de aprender é ler muito código bom. Se você der um código falso que é fácil de entender, mas não é recomendado o uso - é uma maneira ruim de ensinar. Também tenho certeza de que para aqueles que começaram a aprender bash (provavelmente já conhecem alguns bits de outro idioma) entenderão a sintaxe do bash para regex mais facilmente do que algum grepcomando com -Eflag.
Aleks-Daniel Jakimenko-A.

8
@ Aleks-DanielJakimenko Eu passei por este post novamente e agora concordo que é melhor usar o regex do bash. Obrigado por apontar na boa direção, resposta atualizada.
fedorqui 'SO stop prejudicar'

4
Upvote, que permite usá-lo um pouco além do que questão OP, por sh, por exemplo ..
Dereckson

3
@ Aleks-DanielJakimenko usando grep parece ser a melhor opção se você estiver usando sh, fishou outras conchas menos equipados.
tomekwi

47

Na versão 3 do bash, você pode usar o operador '= ~':

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

Referência: http://tldp.org/LDP/abs/html/bashver3.html#REGEXMATCHREF

NOTA: A citação no operador correspondente entre colchetes duplos, [[]], não é mais necessária a partir do Bash versão 3.2


20
Você não deve usar char "no expresion regular porque quando eu uso expresion não funciona?
Dawid Drozd

Além disso, a fuga de barra invertida de {e} é igualmente problemática.
kbulgrien

32

Uma boa maneira de testar se uma string é uma data correta é usar a data do comando:

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

do comentário: pode-se usar a formatação

if [ "2017-01-14" == $(date -d "2017-01-14" '+%Y-%m-%d') ] 

9
Avalie sua resposta com alto nível, pois permite que a função de data lide com as datas e não com as regexs propensas a erros ''
Ali

Isso é bom para verificar opções amplas de data, mas se você precisar verificar um formato de data específico, ele pode fazer isso? Por exemplo, se eu fizer, date -d 2017-11-14eele retornará Ter 14 de novembro 05:00:00 UTC 2017, mas isso quebraria meu script.
1937 Josias

1
Você pode usar algo assim: se ["2017-01-14" == $ (date -d "2017-01-14" '+% Y-% m-% d')] Testa se a data está correta e verifique se o resultado é o mesmo que os dados inseridos. A propósito, tenha muito cuidado com o formato de data localizado (mês-dia-ano vs. dia-mês-ano, por exemplo) #
1155 Django Janny

1
Pode não funcionar, dependendo da sua localidade. Datas americanas formatado usando MM-DD-AAAA não irá funcionar em qualquer outro lugar no mundo, usando DD-MM-AAAA (Europa) ou AAAA-MM-DD (alguns lugares na Ásia)
Paul

@Paul, o que pode não funcionar? Como escrito em um comentário, pode-se usar as opções de formatação ...
Betlista

4

Eu usaria em expr matchvez de =~:

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

Isso é melhor do que a resposta de uso atualmente aceita, =~porque =~também corresponderá a cadeias vazias, o que IMHO não deveria. Suponha que badvarnão esteja definido, depois [[ "1234" =~ "$badvar" ]]; echo $?dê (incorretamente) 0, enquanto expr match "1234" "$badvar" >/dev/null ; echo $?dá o resultado correto 1.

Temos que usar >/dev/nullpara ocultar expr matcho valor de saída , que é o número de caracteres correspondidos ou 0 se nenhuma correspondência for encontrada. Observe que seu valor de saída é diferente de seu status de saída . O status de saída é 0 se houver uma correspondência encontrada ou 1 caso contrário.

Geralmente, a sintaxe para expré:

expr match "$string" "$lead"

Ou:

expr "$string" : "$lead"

onde $leadé uma expressão regular. Sua exit statusserá verdadeiro (0) se leadpartidas a principal fatia de string(há um nome para isso?). Por exemplo , expr match "abcdefghi" "abc"sai true, mas expr match "abcdefghi" "bcd"sai false. (Agradecemos a @Carlo Wood por apontar isso.


7
=~não está combinando cadeias vazias, você está combinando uma cadeia com um padrão vazio no exemplo que você fornece. A sintaxe é string =~ patterne um padrão vazio corresponde a tudo.
precisa saber é o seguinte

2
Isto não corresponde a uma substring, ele retorna (para stdout) o número de principais personagens que combinava e o estado de saída é verdadeira sse pelo menos 1 personagem foi correspondido. É por isso que uma string vazia (que corresponde a 0 caracteres) tem um status de saída false. Por exemplo expr match "abcdefghi" "^" && echo Matched || echo No match- e expr match "abcdefghi" "bcd" && echo Matched || echo No match- ambos retornam "0\nNo match". Onde a correspondência "a.*f"retornará "6\nMatched". O uso do '^' no seu exemplo é, portanto, também desnecessário e já está implícito.
Carlo Wood

@bstpierre: o ponto aqui não é se alguém pode racionalizar o comportamento de =~combinar cadeias vazias. É que esse comportamento pode ser inesperado e pode causar erros. Escrevi esta resposta especificamente porque fui queimada por ela.
Penghe Geng 30/06/19

@PengheGeng Comportamento inesperado? Se um padrão não possui definição ou restrições, na verdade corresponde a qualquer coisa. A ausência de um padrão é compatível com tudo. Escrever código robusto é a resposta, não justificando uma explicação ruim.
Anthony Rutledge

O "código robusto" do @AnthonyRutledge exige o melhor uso das ferramentas disponíveis para evitar erros acidentais de codificação. No código Shell, em que uma variável vazia pode ser introduzida de maneira fácil e acidental a qualquer momento por meio de erros de ortografia, não acho que permitir a correspondência de variáveis ​​vazias seja um recurso robusto. Aparentemente, o autor do GNU exprconcorda comigo.
Penghe Geng 9/07/19

0

Onde o uso de uma regex pode ser útil para determinar se a sequência de caracteres de uma data está correta, ela não pode ser usada facilmente para determinar se a data é válida. Os exemplos a seguir passarão a expressão regular, mas são todas datas inválidas: 20180231, 20190229, 20190431

Portanto, se você deseja validar se sua sequência de datas (vamos chamá-la datestr) está no formato correto, é melhor analisá-la datee pedir datepara converter a sequência no formato correto. Se as duas strings forem idênticas, você terá um formato e uma data válidos.

if [[ "$datestr" == $(date -d "$datestr" "+%Y%m%d" 2>/dev/null) ]]; then
     echo "Valid date"
else
     echo "Invalid date"
fi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.