Check if a string matches a regex in Bash script

One of the arguments that my script receives is a date in the following format: yyyymmdd.

I want to check if I get a valid date as an input.

How can I do this? I am trying to use a regex like: [0-9]\{\8}

Answers


You can say:

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

Or more accurate:

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

That is, you can define a regex in bash matching the format you want. This way you can do:

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

Note this is based on the solution by Aleks-Daniel Jakimenko in User input date format verification in bash.


In other shells you can use grep. If your shell is POSIX compliant, do

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

In fish, which is not POSIX-compliant, you can do

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

In bash version 3 you can use the '=~' operator:

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

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

NOTE: The quoting in the matching operator within the double brackets, [[ ]], is no longer necessary as of Bash version 3.2


A good way to test if a string is a correct date is to use the command 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

I would use expr match instead of =~:

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

This is better than the currently accepted answer of using =~ because =~ will also match empty strings, which IMHO it shouldn't. Suppose badvar is not defined, then [[ "1234" =~ "$badvar" ]]; echo $? gives (incorrectly) 0, while expr match "1234" "$badvar" >/dev/null ; echo $? gives correct result 1.

We have to use >/dev/null to hide expr match's output value, which is the number of characters matched or 0 if no match found. Note its output value is different from its exit status. The exit status is 0 if there's a match found, or 1 otherwise.

Generally, the syntax for expr is:

expr match "$string" '$substring'

Or:

expr "$string" : '$substring'

where $substring is a regular expression.


Where the usage of a regex can be helpful to determine if the character sequence of a date is correct, it cannot be used easily to determine if the date is valid. The following examples will pass the regular expression, but are all invalid dates: 20180231, 20190229, 20190431

So if you want to validate if your date string (let's call it datestr) is in the correct format, it is best to parse it with date and ask date to convert the string to the correct format. If both strings are identical, you have a valid format and valid date.

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

Need Your Help

Wicket WAR in Jetty: .html files not on classpath

html jetty wicket classloader war

I deployed a Wicket-based app's .war file to Jetty 7.0.2. The problem is that Jetty copies the classpath to a temp dir, but only copies *.class, so *.html is not available for the classloader and I...

Aitoc layered anvigation in 1-column layout

php xml magento navigation layered

I want to include Aitoc layered navigation pro to my 1-column layout on my magento site. I've been successful in adding the normal layered navigation by inserting this line of code in the content b...