Check if a string matches a regex in Bash script

2020-01-23 11:00发布

问题:

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}

回答1:

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"


回答2:

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



回答3:

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


回答4:

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" "$lead"

Or:

expr "$string" : "$lead"

where $lead is a regular expression. Its exit status will be true (0) if lead matches the leading slice of string (Is there a name for this?). For example expr match "abcdefghi" "abc"exits true, but expr match "abcdefghi" "bcd" exits false. (Credit to @Carlo Wood for pointing out this.



回答5:

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