Syntax error =~ operator in msysgit bash - windows

I'm trying to add a function to my bash_profile for msysgit:
function git-unpushed {
brinfo=$(git branch -v | grep git-branch-name)
if [[ $brinfo =~ ("[ahead "([[:digit:]]*)]) ]]
then
echo "(${BASH_REMATCH[2]})"
fi
}
But I get the following error:
bash: conditional binary operator expected`
bash: syntax error near =~'
From what I can find, the "equals tilde" operator (=~) evaluates as regex in bash.
Why is =~ is throwing an error?
UPDATE: Here's a screenshot of inputting it manually (this is running sh.exe):

I had the same error on Bash 3.1.0 from Git installation on Windows. Ultimately I changed it to:
if echo $var | grep -E 'regexp' > /dev/null
then
...
fi

According to https://groups.google.com/forum/#!topic/msysgit/yPh85MPDyfE this is because msys doesn't ship libregex along with bash. Supposedly if you compile/find an msys built libregex, and put it in the library path, =~ starts working fine.

Update 2015: msysgit is now obsolete.
You should use the bash which comes with git-for-windows.
As mentioned in this answer, it uses a much more recent bash (4.3+), for which the =~ syntax will work.
Original answer (march 2013)
The bash packaged with msysgit might simply be too old to fully support this operator.
It is certainly too old to compare with unquoted regex, as mentioned in "Bash, version 3" and "How do I use regular expressions in bash scripts?":
As of version 3.2 of Bash, expression to match no longer quoted.
Actually, mklement0 mentions in the comments:
=~ was introduced in bash 3.0 and always supported an unquoted token on the RHS.
Up to 3.1.x, quoted tokens were treated the same as unquoted tokens: both were interpreted as regexes.
What changed in 3.2 was that quoted tokens (or quoted substrings of a token) are now treated as literals.
But I tried with quotes (in the latest msysgit 1.8.1.2), and it still fails:
vonc#voncvb /
$ /bin/bash --version
GNU bash, version 3.1.0(1)-release (i686-pc-msys)
Copyright (C) 2005 Free Software Foundation, Inc.
vonc#voncvb /
$ variable="This is a fine mess."
vonc#voncvb /
$ echo "$variable"
This is a fine mess.
vonc#voncvb /
$ if [[ "$variable" =~ T.........fin*es* ]] ; then echo "ok" ; fi
bash: conditional binary operator expected
bash: syntax error near `=~'
vonc#voncvb /
$ if [[ "$variable" =~ "T.........fin*es*" ]] ; then echo "ok" ; fi
bash: conditional binary operator expected
bash: syntax error near `=~'
vonc#voncvb /

Here is a solution that supports extracting matched strings. If the operator =~ is not supported by bash, then the sed command is used (installed with msysgit)
if eval "[[ a =~ a ]]" 2>/dev/null; then
regexMatch() { # (string, regex)
eval "[[ \$1 =~ \$2 ]]"
return $?
}
elif command -v /bin/sed >/dev/null 2>&1; then
regexMatch() { # (string, regex)
local string=$1
if [[ ${2: -1} = $ ]]; then
local regex="(${2%$})()()()()()()()()$"
else
local regex="($2)()()()()()()()().*"
fi
regex=${regex//\//\\/}
local replacement="\1\n\2\n\3\n\4\n\5\n\6\n\7\n\8\n\9\n"
local OLD_IFS=$IFS
IFS=$'\n'
BASH_REMATCH=($(echo "$string" | /bin/sed -rn "s/$regex/$replacement/p" | while read -r; do echo "${REPLY}"; done))
IFS=$OLD_IFS
[[ $BASH_REMATCH ]] && return 0 || return 1
}
else
error "your Bash shell does not support regular expressions"
fi
Usage example:
if regexMatch "username#host.domain" "(.+)#(.+)"; then
echo ${BASH_REMATCH[0]}
echo ${BASH_REMATCH[1]}
echo ${BASH_REMATCH[2]}
fi

Related

GitLab CI: Bash if Statement in Job wrong result

I have a GitLab job with a bash if statement that looks like this
script:
- echo $NEW_VERSION
- export STAGE=staging
- |-
if [[ $(expr match "$NEW_VERSION", '([0-9]+)\.([0-9]+)\.([0-9]+)$') != 0 ]]; then
export STAGE=production;
fi
- echo $STAGE
The variable $NEW_VERSION comes from a previous step. The content of this variable is a semantic version string like 1.0.0 or 1.0.1-develop.1. If this variable is a prerelease (it contains the develop suffix) I want to set the $STAGE to staging otherwise to production.
My problem is that no matter which content the $NEW_VERSION variable has, $STAGE is always set to staging.
If I execute the script on my local mac the value is set right.
Here the log output:
$ echo $NEW_VERSION
11.0.0
$ export STAGE=staging
$ if [[ $(expr match "$NEW_VERSION", '([0-9]+)\.([0-9]+)\.([0-9]+)$') != 0 ]]; then # collapsed multi-line command
staging
Has anyone experienced a similar problem or has an idea why this solution dosen't work?
It's simpler to use grep.
if printf "%s\n" "$NEW_VERSION" | grep -xq '[0-9]\+\.[0-9]\+\.[0-9]\+'; then
It seems to be caused by a wrong syntax in the regular expression and the extra comma after "$NEW_VERSION" (which is appended to the value of the variable). This version works as expected.
echo $NEW_VERSION
export STAGE=staging
if [ "$(expr match "$NEW_VERSION" '^[0-9]\+\.[0-9]\+\.[0-9]\+$')" != 0 ]; then
export STAGE=production
fi
echo $STAGE
I prefer the pure bash version with =~ operator.
Verify that you are really use bash if does'nt work.
echo $NEW_VERSION
export STAGE=staging
if [[ "${NEW_VERSION}" =~ ([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
export STAGE=production
fi
echo $STAGE
Remarks:
Use double brackets with =~ operator
Do not put regular expression in quotes
Bonus:
=~ is an internal operator, so it's faster than a expr call
(tested with GNU bash, version 5.0.17)

Fixing POSIX sh warning in a small Bash program

I wrote the following code in Bash:
#!/bin/sh
host=$1
regex="^(((git|ssh|http(s)?)|(git#[\w\.]+))(:(\/\/)?)([A-Za-z0-9.#:_/-]+)\.com)(.*)"
if [[ "$host" =~ $regex ]]; then
d=${BASH_REMATCH[1]}
if [[ "$d" = *github* ]]; then
return
fi
fi
die "Current repository is not stored in Github."
I want to learn how to write a better Bash code so I use the shellcheck.net.
Line 5:
if [[ "$host" =~ $regex ]]; then
^-- SC2039: In POSIX sh, [[ ]] is undefined.
Line 6:
d=${BASH_REMATCH[1]}
^-- SC2039: In POSIX sh, array references are undefined.
Line 7:
if [[ "$d" = *github* ]]; then
^-- SC2039: In POSIX sh, [[ ]] is undefined.
I'm trying to understand how to fix those warnings. I understand that in order to fix [[ ]] I need it to switch to [ ] but then I get an error due globs. Also how should I replace the =~ operator?
When you write #!/bin/sh then you shouldn't use bash-specific features like [[. But you don't need to change [[ to [ or anything like that; just change the shebang line to #!/bin/bash. Then you can use all the bash features you like.
Use grep and sed in posix.
# use grep -q to match with regex
if printf "%s\n" "$host" | grep -q '\(git\|ssh\|http\(s\)\)etc. etc. etc.'; then
# use sed to extract part of the string matching regex
d=$(printf "%s\n" "$host" | sed 's/\(g\|ssh\|http\(s\)\)etc. etc. etc./\2/')
if printf "%s\n" "$d" | grep -q github; then
return
fi
fi
Finding out proper regexes is left to others.
You could try to parse out the different parts with parameter expansions though it's going to get a bit tedious. (The link is to the Bash manual; only a few of the expansions supported by Bash are POSIX.)
Assuming the input is a valid, well-formed URL (which may or may not be warranted) maybe try
host=$1
tail=${1#*://*/}
case $tail in "$host") tail=${host#*/};; esac
case ${host%/$tail} in
*github.com) return ;;
esac
die "Current repository is not stored in Github."
(where of course we assume that this is in a context where return makes sense, and where die is defined separately, like we have to assume in the original code).
This is quite a lot simpler than the regex you presented, and definitely does not cover all the strings that the regex would be able to handle; but perhaps it doesn't have to be all that complex if we can assume that the URL has gone through some sort of validation (i.e. if it's the output from git remote it's pretty safe to assume that the user has verified it by other means already).

getting sh: =~: unknown operand in shell scripting

I just started doing shell scripts and getting unknown operand error while using regex in if statement. i searched google but did not get anything
IP="172.21.1.1"
if [[ "$IP" =~ /d ]] ; then
echo "qqq"
fi
Getting error as
sh: =~: unknown operand
Bash version is : BusyBox v1.19.3 (2012-01-31 08:57:52 PST) built-in shell (ash)
This is happening because the operator =~ doesn't existe for bash.
As see you are trying to use a Regex to compare your variables. I recommend to use the expr command. Here is an example:
IP="172.21.1.1"
if [[ $(expr match "$IP" 'my_regex') != 0 ]]; then echo "qqq"; fi;

Script to extract a part of string

ns/APAC_BankTransfers_Publish/CMB/services/svcPublishBankTransfers/flow.xml
In the above line and similar lines like this I want to extract whatever is present in between services and flow.xml and save it to a variable DIST.
The output should be svcPublishBankTransfers.
Using parameter expansion mechanisms available in POSIX sh:
s=ns/APAC_BankTransfers_Publish/CMB/services/svcPublishBankTransfers/flow.xml
s=${s%/flow.xml} # remove "/flow.xml"
s=${s##*/services/} # remove everything before "services"
echo "$s"
This has the advantages of being purely in-process (so faster than approaches that require piping through an external tool), and compatible with all POSIX shells (ash, dash, ksh, etc).
References:
BashFAQ #100 ("How do I do string manipulation in bash?")
BashFAQ #73 ("How can I use parameter expansion? How can I get substrings?")
Using BASH regex:
s='ns/APAC_BankTransfers_Publish/CMB/services/svcPublishBankTransfers/flow.xml'
[[ "$s" =~ /([^/]+)/[^/]*$ ]] && echo "${BASH_REMATCH[1]}"
svcPublishBankTransfers
OR else:
[[ "$s" =~ /services/([^/]+)/flow\.xml ]] && echo "${BASH_REMATCH[1]}"
svcPublishBankTransfers
bash$ echo "ns/APAC_BankTransfers_Publish/CMB/services/svcPublishBankTransfers/flow.xml" | cut -d '/' -f 5
svcPublishBankTransfers
bash$

String manipulation Bash-Extensions

In my script I'm trying to take a string, then output the extension of a string if it has one. So essentially I take the basename of a file, then output anything that comes after a period.
What's the syntax to do something like this?
For example
dotcutter.sh
file=testfile.jpg
the script should output .jpg
EDIT:
I've solved my problem now with:
file=$(basename "$1" )
stub=${file%.*}
echo ${file#"$stub"}
Which reduces my argument to a basename, thank you all.
$ file=testfile.jpg
$ echo ${file##*.}
jpg
$ file=testfile.ext1.ext2
$ echo ${file##*.}
ext2
$ file=noextension
$ echo ${file##*.}
noextension
Notice that it doesn't work if the file has no extension. If that's important then try this two-step solution:
$ file=testfile.ext1.ext2
$ stub=${file%.*}
$ echo ${file#"$stub"}
.ext2
Or this regex-based one, which will only call echo if there's actually an extension. (&& is shorthand for "if then".)
$ regex='(\.[^.]*)$'
$ file=testfile.ext1.ext2
$ [[ $file =~ $regex ]] && echo ${BASH_REMATCH[1]}
.ext2
$ file=noextension
$ [[ $file =~ $regex ]] && echo ${BASH_REMATCH[1]}
See also:
basename(1)
dirname(1)
Bash FAQ: How can I use parameter expansion? How can I get substrings? How can I get a file without its extension, or get just a file's extension?]
Try the following code :
file=testfile.jpg
echo .${file#*.}
That use parameter expansion

Resources