SVN pre-commit hook logic - regex

I'm adding a logic in my svn pre-commit hook to check if there is QA(in upper case starting with space) in commit message then commit should fail. But its not working. Kindly assist me how to write it properly.
REPOS="$1"
TXN="$2"
# Make sure that the log message contains some text.
SVNLOOK=/usr/bin/svnlook
LOGMSG=$($SVNLOOK log -t "$TXN" "$REPOS")
# check if any comment has supplied by the commiter
if [ -z "$LOGMSG" ]; then
echo "Your commit was blocked because it have no comments." 1>&2
exit 1
fi
#check minimum size of text
if [ ${#LOGMSG} -lt 15 ]; then
echo "Your Commit was blocked because the comments does not meet minimum length requirements (15 letters)." 1>&2
exit 1
fi
# get TaskID by regex
TaskID=$(expr "$LOGMSG" : '\([#][0-9]\{1,9\}[:][" "]\)[A-Za-z0-9]*')
# Check if task id was found.
if [ -z "$TaskID" ]; then
echo "" 1>&2
echo "No Task id found in log message \"$LOGMSG\"" 1>&2
echo "" 1>&2
echo "The TaskID must be the first item on the first line of the log message." 1>&2
echo "" 1>&2
echo "Proper TaskID format--> #123- 'Your commit message' " 1>&2
exit 1
fi
#Check that QA should not be present in log message.
QA=$(expr "$LOGMSG" : '\(*[" "][QA][" "]\)')
if [ "$QA" == "QA" ]; then
echo "" 1>&2
echo "Your log message \"$LOGMSG\" must not contain QA in upper case." 1>&2
echo "" 1>&2
exit 1
fi

The regex is incorrect:
\( starts a capturing group in expr, but you don't need a capturing group for your task
When * follows a \( in a pattern, it tries to match a literal *
[QA] matches a single character, which can be Q or A
The pattern of expr must match from the start of the string
As it is, the regex doesn't correspond to your requirement.
Even if the above points are fixed, a pattern QA, "QA" with spaces around it, will not match commit messages like this:
"Fix the build of QA"
"Broken in QA, temporarily"
... and so on...
That is, instead of "QA" with spaces around, you probably want to match QA with word boundaries around.
This is easy to do using grep -w QA.
As you clarified in a comment, you really want a space before the "Q".
In that case the -w flag of grep is not suitable,
because that requires a word boundary at both sides of patterns.
There is another way to match word boundaries,
using \< for word start and \> for word end.
So to have a space in front of "Q",
and a word boundary after "A", you can write QA\>, like this:
if grep -q ' QA\>' <<< "$LOGMSG"; then
echo
echo "Your log message \"$LOGMSG\" must not contain QA in upper case."
echo
exit 1
fi 1>&2
Notice some other improvements:
Instead of redirecting to stderr every single echo, you can redirect the entire if statement
Instead of echo "" you can write simply echo
Instead of storing the result of a command in a temporary variable, you can write conditionals on the exit code of commands

This could be an error with your regex expression checking for " QA ".
I find using this site pretty useful for testing out regex expressions - RegExr.
I put your (*[" "][QA][" "]) expression into the site and when I looked at the details of it (a tab link towards the bottom of the page), it would break down exactly what you regular expression would match with. From this, it was saying that it was looking for the following:
0 or more (
Either a " or a space
Either Q or A (not both)
Either a " or a space
Ending with a )
I put the following expression into it - ( (QA) ) and it was able to find the match in a sample svn message (TEST-117 QA testing message).

Related

Regular expressions don't work as expected in bash if-else block's condition

My pattern defined to match in if-else block is :
pat="17[0-1][0-9][0-9][0-9].AUG"
nln=""
In my script, I'm taking user input which needs to be matched against the pattern, which if doesn't match, appropriate error messages are to be shown. Pretty simple, but giving me a hard time though. My code block from the script is this:
echo "How many days' AUDIT Logs need to be searched?"
read days
echo "Enter file name(s)[For multiple files, one file per line]: "
for(( c = 0 ; c < $days ; c++))
do
read elements
if [[ $elements =~ $pat ]];
then
array[$c]="$elements"
elif [[ $elements =~ $nln ]];
then
echo "No file entered.Run script again. Exiting"
exit;
else
echo "Invalid filename entered: $elements.Run script again. Exiting"
exit;
fi
done
The format I want from the user for filenames to be entered is this:
170402.AUG
So basically yymmdd.AUG (where y-year,m-month,d-day), with trailing or leading spaces is fine. Anything other than that should throw "Invalid filename entered: $elements.Run script again. Exiting" message. Also I want to check if if it is a blank line with a "Enter" hit, it should give an error saying "No file entered.Run script again. Exiting"
However my code, even if I enter something like "xxx" as filename, which should be throwing "Invalid filename entered: $elements.Run script again. Exiting", is actually checking true against a blank line, and throwing "No file entered.Run script again. Exiting"
Need some help with handling the regular expressions' check with user input, as otherwise rest of my script works just fine.
I think as discussed in the comments you are confusing with the glob match and a regEx match, what you have defined as pat is a glob match which needs to be equated with the == operator as,
pat="17[0-1][0-9][0-9][0-9].AUG"
string="170402.AUG"
[[ $string == $pat ]] && printf "Match success\n"
The equivalent ~ match would be to something as
pat="17[[:digit:]]{4}\.AUG"
[[ $string =~ $pat ]] && printf "Match success\n"
As you can see the . in the regex syntax has been escaped to deprive of its special meaning ( to match any character) but just to use as a literal dot. The POSIX character class [[:digit:]] with a character count {4} allows you to match 4 digits followed by .AUG
And for the string empty check do as suggested by the comments from Cyrus, or by Benjamin.W
[[ $elements == "" ]]
(or)
[[ -z $elements ]]
I would not bug the user with how many days (who want count 15 days or like)? Also, why only one file per line? You should help the users, not bug them like microsoft...
For the start:
show_help() { cat <<'EOF'
bla bla....
EOF
}
show_files() { echo "${#files[#]} valid files entered: ${files[#]}"; }
while read -r -p 'files? (h-help)> ' line
do
case "$line" in
q) echo "quitting..." ; exit 0 ;;
h) show_help ; continue;;
'') (( ${#files} )) && show_files; continue ;;
l) show_files ; continue ;;
p) (( ${#files} )) && break || { echo "No files enterd.. quitting" ; exit 1; } ;; # go to processing
esac
# select (grep) the valid patterns from the entered line
# and append them into the array
# using the -P (if your grep know it) you can construct very complex regexes
files+=( $(grep -oP '17\d{4}.\w{3}' <<< "$line") )
done
echo "processing files ${files[#]}"
Using such logic you can build really powerful and user-friendly app. Also, you can use -e for the read enable the readline functions (cursor keys and like)...
But :) Consider just create a simple script, which accepts arguments. Without any dialogs and such. example:
myscript -h
same as above, or some longer help text
myscript 170402.AUG 170403.AUG 170404.AUG 170405.AUG
will do whatever it should do with the files. Main benefit, you could use globbing in the filenames, like
myscript 1704*
and so on...
And if you really want the dialog, it could show it when someone runs the script without any argument, e.g.:
myscript
will run in interactive mode...

While-Loop BASH-Regex not matching

Why is this INCREDIBALLY simple REGEX not matching?!!?
#!/bin/bash
while true
do
read -r -p $'What is the JIRA Ticket associated with this work?' JIRA
#Use a regular expresion to verify that our reply stored in JIRA is only 4 digits, if not, loop and try again.
if [[ ! "$JIRA" =~ [0-9]{4} ]]
then
echo -en "The JIRA Ticket should only be 4 digits\nPlease try again."
continue
else
break 1
fi
done
When prompted, if you type "ffffff" it catches, but if you type more than 4 digits "444444" or even toss a letter in there "4444444fffff" it catches nothing, hits the else block and quits. I think this is basic and I'm dumbfounded as to why its not catching the extra digits or characters?
I appreciate the help.
You need to change your equality test to:
if [[ ! "$JIRA" =~ ^[0-9]{4}$ ]]
This ensures that the entire string contains just four digits. ^ means beginning of string, $ means end of string.
The regular expression is open-ended, meaning it only has to match a substring of the left-hand argument, not the entire thing. Anchor your regular expression to force it to match the entire string:
if [[ ! "$JIRA" =~ ^[0-9]{4}$ ]]
Maybe a simpler pattern (== instead of =~) may solve your issue:
#!/bin/bash
while true
do
read -r -p $'What is the JIRA Ticket associated with this work?' JIRA
[[ $JIRA == [0-9][0-9][0-9][0-9] ]] && break 1
echo -en "The JIRA Ticket should only be 4 digits\nPlease try again."
done

bash regular expression format

My code have problem with compare var with regular expression.
The main problem is problem is here
if [[ “$alarm” =~ ^[0-2][0-9]\:[0-5][0-9]$ ]]
This "if" is never true i dont know why even if i pass to "$alarm" value like 13:00 or 08:19 its always false and write "invalid clock format".
When i try this ^[0-2][0-9]:[0-5][0-9]$ on site to test regular expressions its work for example i compered with 12:20.
I start my script whith command ./alarm 11:12
below is whole code
#!/bin/bash
masa="`date +%k:%M`"
mp3="$HOME/Desktop/alarm.mp3" #change this
echo "lol";
if [ $# != 1 ]; then
echo "please insert alarm time [24hours format]"
echo "example ./alarm 13:00 [will ring alarm at 1:00pm]"
exit;
fi
alarm=$1
echo "$alarm"
#fix me with better regex >_<
if [[ “$alarm” =~ ^[0-2][0-9]\:[0-5][0-9]$ ]]
then
echo "time now $masa"
echo "alarm set to $alarm"
echo "will play $mp3"
else
echo "invalid clock format"
exit;
fi
while [ $masa != $alarm ];do
masa="`date +%k:%M`" #update time
sleep 1 #dont overload the cpu cycle
done
echo $masa
if [ $masa = $alarm ];then
echo ringggggggg
play $mp3 > /dev/null 2> /dev/null &
fi
exit
I can see a couple of issues with your test.
Firstly, it looks like you may be using the wrong kind of double quotes around your variable (“ ”, rather than "). These "fancy quotes" are being concatenated with your variable, which I assume is what causes your pattern to fail to match. You could change them but within bash's extended tests (i.e. [[ instead of [), there's no need to quote your variables anyway, so I would suggest removing them entirely.
Secondly, your regular expression allows some invalid dates at the moment. I would suggest using something like this:
re='^([01][0-9]|2[0-3]):[0-5][0-9]$'
if [[ $alarm =~ $re ]]
I have deliberately chosen to use a separate variable to store the pattern, as this is the most widely compatible way of working with bash regexes.

Git hook commit-msg to ensure the commit message contains a string that conforms to a regex

I'm trying to make bash script to be used as a git commit-msg hook. Essentially I want to ensure that the commit message contains a string which matches the regex. My regex is correct, but i'm not sure how to check if the message contained in .git/COMMIT_EDITMSG matches the regex.
#!/bin/sh
#
# This hook verifies that the commit message contains a reference to a tfs item
RED=$(tput setaf 1)
NORMAL=$(tput sgr0)
# Regex to validate a string contains "#" followed by 4 or 5 digits anywhere in the commit message
regex="#[0-9]{4,5}($|[^0-9])"
# I NEED TO FIGURE OUT HOW TO READ THE CONTENTS OF THIS FILE AND ENSURE IT MATCHES THE REGEX
echo "MSG = $1" # This prints "MSG = .git/COMMIT_EDITMSG"
# If the commit message does not match the regex
if ! [[ $1 =~ $regex ]]; then
echo "${RED}ERROR - Missing tfs item in commmit message.$NORMAL"
exit 1
else
echo "MESSAGE IS GOOD?"
fi
exit 0
I figured it out with this:
#!/bin/sh
#
# This hook verifies that the commit message contains a reference to a tfs item
RED=$(tput setaf 1)
NORMAL=$(tput sgr0)
# Regex to validate a string contains "#" followed by 4 or 5 digits anywhere in the commit message
regex="#[0-9]{4,5}($|[^0-9])"
file=`cat $1` # The file that contains the commit message
# If the commit message does not match the regex
if ! [[ $file =~ $regex ]]; then
echo "${RED}ERROR - Missing tfs item in commmit message.$NORMAL"
exit 1
else
echo "MESSAGE IS GOOD?"
fi
exit 0

bash conditional statement OR regex

I have a test file that logs the number of failures on the last line. I determine whether the test passed by checking for this line. Due to a bug a passed test will always have one failure. A passed line looks like the following:
FAILED : - Failures = 1
My code to date which works is:
if [[ $(tail -n 1 temp.txt) != "FAILED : - Failures = 1" ]]; then
echo "FAILED"
fi
However with a different software branch the word 'FAILED' has been replaced with 'WARNING' e.g.
WARNING : - Failures = 1
I'm trying to write a regex to OR the words 'WARNING' and 'FAILED' but so far it's not working.
You can use extglob:
if [[ $(tail -n 1 temp.txt) != #(WARNING|FAILED)" : - Failures = 1" ]]
then
echo "FAILED"
fi
#(pattern1|pattern2|..) is a bash glob that matches either of the given patterns.
Depending on your input, you can use further pattern matching to make your matching more resilient. For example, strings containing the word "WARNING" or "FAILED" followed by the word "Failures" can be matched with
[[ $(tail -n 1 temp.txt) != *#(WARNING|FAILED)*"Failures"* ]]
Fun fact: extglobs are regular expressions, they just don't use the canonical syntax that other UNIX tools do.