I have function like following to be able to validate two dates passed to it:
function validate_dates() {
# validate date format to be yyyy-mm-dd
local regex="^[[:digit]]{4}-[[:digit]]{2}-[[:digit]]{2}$"
local dates=( "$1" "$2" )
printf "%s\n" "${dates[#]}"
for __date in "$dates"
do
echo "$__date"
[[ $__date =~ $regex ]] || error_exit "One of dates is malformed!" # error_exit is just function helper to exit with message
done
}
However when I call function -
validate_dates "2013-05-23" "2014-07-28"
I get:
2013-05-23
2014-07-28
2013-05-23
One of dates is malformed!
Why does it break on correctly formatted date?
"^[[:digit]]{4}-[[:digit]]{2}-[[:digit]]{2}$"
probably should be:
"^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}$"
You missed some colons.
Also, for __date in "$dates" perhaps should be for __date in "${dates[#]}". "$dates" would only expand to the first element of the array.
If you want to have more than just 2 parameters or have variable parameters, change
local dates=( "$1" "$2" )
to
local dates=("$#")
My version:
function validate_dates {
local regex='^[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}$'
printf "%s\n" "$#"
for __; do
[[ $__ =~ $regex ]] || error_exit "$__ is an invalid date."
done
}
Please note you cannot "validate date" using only regex. A second pass using date might be useful:
for __date in "$dates"
do
echo "$__date"
{ [[ $__date =~ $regex ]] && (date -d "$__date" > /dev/null 2>&1) } ||
error_exit "One of dates is malformed!" # error_exit is just function helper to exit with message
done
Using grep with -P makes the regex much easier to read, and you can simplify your function as below:
function validate_dates {
for i in $#; do
grep -qP '^\d{4}-\d{2}-\d{2}$' <<< $i ||\
error_exit "One of dates is malformed!"
done
}
Related
I'm trying to solve a problem that appeared in my script which doesn't let me match the date+time (YYYY-MM-DD HH:MM:SS) inside a for loop
list='"dt_txt":"2022-06-03 21:00:00"},'
regex_datehour='"dt_txt":"([0-9,-]*.[0-9,:]*)'
for i in $list; do
[[ $i =~ $regex_datehour ]] && echo "${BASH_REMATCH[1]}"
done
It seems that the "." between the two pair of brackets it's not recognizing the space! that's because inside of the list, if I replace the empty space between the date and the time by a _, it works as intended! list='"dt_txt":"2022-06-03_21:00:00"},'
desired output:
2022-06-03 21:00:00
what I get:
2022-06-03
The problem here is one that catches a lot of people, and that is whitespace breaking. In the for loop, your $list variable is not quoted, and it contains a space:
$ list='"dt_txt":"2022-06-03 21:00:00"},'
$ for i in $list ; do echo "i = $i" ; done ;
i = "dt_txt":"2022-06-03
i = 21:00:00"},
Make sure to put double-quotes around all strings that contain variables except regexes:
Using an array for list, which is what makes sense when using the for loop from your original code, it would look something like this:
#!/usr/bin/env bash
# filename: re.sh
list=(
'"dt_txt":"2022-06-03 21:00:00"},'
'"dt_txt":"2022-06-03 22:00:00"},'
'"dt_txt":"2022-06-03 23:00:00"},'
)
regex_datehour='"dt_txt":"([0-9,-]*.[0-9,:]*)'
for i in "${list[#]}" ; do
[[ "$i" =~ $regex_datehour ]] && echo "${BASH_REMATCH[1]}"
done
$ ./re.sh
2022-06-03 21:00:00
2022-06-03 22:00:00
2022-06-03 23:00:00
I using bash and I have string (a commit message)
:sparkles: feat(xxx): this is a commit
and I want to divide it into variables sections:
emoji=:sparkles:
type=feat
scope=xxx
message=this is a commit
I try to use grep, but the regex is not return what I need (for example the "type") and anyway how to paste it into variables?
echo ":sparkles: feat(xxx): this is a commit" | grep "[((.*))]"
With bash version >= 3, a regex and an array:
x=":sparkles: feat(xxx): this is a commit"
[[ "$x" =~ ^(:.*:)\ (.*)\((.*)\):\ (.*)$ ]]
echo "${BASH_REMATCH[1]}"
echo "${BASH_REMATCH[2]}"
echo "${BASH_REMATCH[3]}"
echo "${BASH_REMATCH[4]}"
Output:
:sparkles:
feat
xxx
this is a commit
From man bash:
BASH_REMATCH: An array variable whose members are assigned by the =~ binary operator to the [[ conditional command. The element
with index 0 is the portion of the string matching the entire regular expression. The element with index n is the
portion of the string matching the nth parenthesized subexpression. This variable is read-only.
I have a string like this
foo:collection:indexation [options] [--] <text> <text_1> <text_2> <text_3> <text_4>
And i want to use bash regex to get an array or string that I can split to get this in order to check if the syntax is correct
["text", "text_1", "text_2", "text_3", "text_4"]
I have tried to do this :
COMMAND_OUTPUT=$($COMMAND_HELP)
# get the output of the help
# regex
ARGUMENT_REGEX="<([^>]+)>"
GOOD_REGEX="[a-z-]"
# get all the arguments
while [[ $COMMAND_OUTPUT =~ $ARGUMENT_REGEX ]]; do
ARGUMENT="${BASH_REMATCH[1]}"
# bad syntax
if [[ ! $ARGUMENT =~ $GOOD_REGEX ]]; then
echo "Invalid argument '$ARGUMENT' for the command $FILE"
echo "Must only use characters [a-z:-]"
exit 5
fi
done
But the while does not seem to be appropriate since I always get the first match.
How can I get all the matches for this regex ?
Thanks !
The loop doesn't work because every time you're just testing the same input string against the regexp. It doesn't know that it should start scanning after the match from the previous iteration. You'd need to remove the part of the string up to and including the previous match before doing the next test.
A simpler way is to use grep -o to get all the matches.
$COMMAND_HELP | grep -o "$ARGUMENT_REGEX" | while read ARGUMENT; do
if [[ ! $ARGUMENT =~ $GOOD_REGEX ]]; then
echo "Invalid argument '$ARGUMENT' for the command $FILE"
echo "Must only use characters [a-z:-]"
exit 5
fi
done
Bash doesn't have this directly, but you can achieve a similar effect with a slight modification.
string='foo...'
re='<([^>]+)>'
while [[ $string =~ $re(.*) ]]; do
string=${BASH_REMATCH[2]}
# process as before
done
This matches the regex we want and also everything in the string after the regex. We keep shortening $string by assigning only the after-our-regex portion to it on every iteration. On the last iteration, ${BASH_REMATCH[2]} will be empty so the loop will terminate.
I am trying to fix the users input if they input an incorrect date format that is not (YYYY-MM-DD) but I cant figure it out. Here is what I have:
while [ "$startDate" != "^[0-9]{4}-[0-9]{2}-[0-9]{2}$" ]
do
echo "Please retype the start date (YYYY-MM-DD):"
read startDate
done
Instead of !=, you have to use ! $var =~ regex to perform regex comparisons:
[[ $date =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]
^^
So that your script can be like this:
date=""
while [[ ! $date =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; do
echo "enter date (YYYY-MM-DD)"
read $date
done
Why not convert the incorrect format to desired one using date program:
$ date -d "06/38/1992" +"%Y-%m-%d"
1992-06-28
You can also check for conversion failure by checking return value.
$ date -d "06/38/1992" +"%Y-%m-%d"
date: invalid date ‘06/38/1992’
$ [ -z $? ] || echo "Error, failed to parse date"
Looking for a quick way in BASH to test whether my variable is single or multiline? I thought the following would work but it always comes back as no
input='foo
bar'
regex="\n" ; [[ $regex =~ "${input}" ]] && { echo 'yes' ; } || { echo 'no' ; }
You don't need regex as you can use glob pattern to check for this:
[[ $str == *$'\n'* ]] && echo "multiline" || echo "single line"
$str == *$'\n'* will return true if any newline is found in $str.
Change your regex like below,
$ regex="[\\r\\n]"
$ [[ "${input}" =~ $regex ]] && { echo 'yes' ; } || { echo 'no' ; }
yes
You do not need to use a regex. Just erase everything that is not a "new line", and count characters:
str=$'foo\nbar\nbaz'
Not calling any eternal program (pure bash):
b=${str//$'\n'}; echo $(( ${#str} - ${#b} ))
The number printed is the number of new lines.
An alternative solution is to cut the variable at the \n, and find if it got shorter:
b="${str%%$'\n'*}"; (( ${#b} < ${#str} )) && echo "Multiline"
Note that this will fail if the line end is \c (one CR, as in classic MAC).