How to use the =~ operand? - bash

I've bumped into a Nagios check script which has been written by someone who already left my company and there's an operator there which I can't understand it's use.
This is the relevant part from the shell script:
if [[ "$URL" =~ $ACTIVE ]] && [[ "$URL2" =~ $ACTIVE2 ]]; then
echo "OK: $HOST is ACTIVE in the Load Balancer"
exit 0
My question is:
What is this =~?
I've checked about it in the internet and found that it's a bitwise which "Flips the bits in the operand", but I don't understand where and how to use it, can you please elaborate?
Edit #1:
This is the full script:
#!/bin/bash
#Purpose: Checks if proxy is active in the LB
#Date: May 09, 2011
#Variables
HOST=$1
URL=`wget --timeout=60 -w 3 -qO- http://$HOST:8080/proxy/keepalive?file=/workspace/temp/1`
URL2=`wget --timeout=60 -w 3 -qO- http://$HOST:8080/proxy/keepalive?file=/workspace/temp/1.txt`
ACTIVE="1"
ACTIVE2="ppp"
LOG="/tmp/PROXY-LB.log"
#Begin Code
if [ -z $HOST ]; then
echo "You must specify IPADDRESS (e.g. 68.67.174.34)"
exit 3
fi
if [[ "$URL" =~ $ACTIVE ]] && [[ "$URL2" =~ $ACTIVE2 ]]; then
echo "OK: $HOST is ACTIVE in the Load Balancer"
exit 0
else
echo "*** Problem: $HOST is out from the Load Balancer"
echo "`date`: *** HOST $HOST is out from the Load Balancer" >> $LOG 2>&1
exit 2
fi
#END
My question is, couldn't the coder use this (without the ~) instead?
if [[ "$URL" = $ACTIVE ]] && [[ "$URL2" = $ACTIVE2 ]]; then
Edit #2:
Here are some examples I tried:
$ d="hello"
$ [[ "$d" =~ *ll* ]] && echo "yes"
$ [[ "$d" =~ he* ]] && echo "yes"
yes
$ [[ "$d" =~ *lo ]] && echo "yes"
$
Edit #3:
Okay, I've done some more tests and I believe I understand it now:
$ [[ "$d" =~ he* ]] && echo "yes"
yes
$ [[ "$d" =~ *lo ]] && echo "yes"
$ [[ "$d" =~ h* ]] && echo "yes"
yes
$ [[ "$d" =~ lo$ ]] && echo "yes"
yes
$ [[ "$d" =~ ^he ]] && echo "yes"
yes
$ [[ "$d" =~ [a-z]ll[a-z] ]] && echo "yes"
yes
$
Thank you all for your help and information!

It is used to perform comparisons in strings.
if [[ "$URL" =~ $ACTIVE ]] && [[ "$URL2" =~ $ACTIVE2 ]]; then
Checks if $URL contains the content of the variable $ACTIVE and if $URL2 contains the content of the variable $ACTIVE2.
See a test:
$ d="hello"
$ [[ "$d" =~ he* ]] && echo "yes"
yes
$ [[ "$d" =~ *ba* ]] && echo "yes"
$
$ [[ $d =~ .*ll.* ]] && echo "yes"
yes
In the last one you have to indicate the regex properly. It is equivalent to using == and just *ll*.
$ [[ $d == *ll* ]] && echo "yes"
yes
From man bash -> 3.2.4.2 Conditional Constructs:
An additional binary operator, =~, is available, with the same
precedence as == and !=. When it is used, the string to the
right of the operator is considered an extended regular expression and
matched accordingly (as in regex(3)). The return value is 0 if the
string matches the pattern, and 1 otherwise. If the regular
expression is syntactically incorrect, the conditional expression's
return value is 2.

Related

How to grep for multiple strings in the same line inside a file with similar pattern?

I have a file named my.txt:
abc_default_flow
#abc_default_flow -p sam
abc_default_flow -p sam
# abc_default_flow -p david
abc_default_flow -p david -z what_is_it
I want to match a particular line which has multiple strings and want to match the exact line which contains all the strings.
I tried the below piece of code, but as soon as it matches the partial string it comes out of the loop, rather than actual line which contains all the strings.
#!/bin/bash -f
f_name=abc_default_flow
p_name=sam
file_content=./my.txt
#echo "file_content: $file_content"
while IFS= read -r file_line
do
echo $file_line
if [[ $file_line != *"#"* ]] && [[ $file_line != "" ]] && echo $file_line | grep -E "${f_name}|${p_name}"; then
#if [[ $file_line != *"#"* ]] && [[ $file_line != "" ]] && echo $file_line | grep -v "${f_name}\|${p_name}"; then
#if [[ $file_line != *"#"* ]] && [[ $file_line != "" ]] && [[ $file_line =~ $f_name ]] && [[ $file_line =~ $p_name ]]; then
if [[ -z "$p_name" ]]; then
f_name=${f_name}_${p_name}
fi
echo "f_name: ${f_name}"
break
fi
done < $file_content
What would be the right way to grep or use any other process to find the right line within the file?
Update: With the below code I am able to get the output, but is there any simple way with grep or sed or awk to find the result in single line instead of nested if loops.
#!/bin/bash -f
f_name=abc_default_flow
p_name=david
file_content=./my.txt
echo "f_name: $f_name, p_name: $p_name"
while IFS= read -r file_line
do
if [[ $file_line != *"#"* ]] && [[ $file_line != "" ]]; then
echo "l1"
if [[ ! -z "$f_name" ]] && [[ $file_line =~ "$f_name" ]]; then
echo "l2, $file_line, $f_name, $p_name"
if [[ ! -z "$p_name" ]] && [[ $file_line =~ "$p_name" ]]; then
f_name=${f_name}_${p_name}
echo "f_name: ${f_name}"
break
elif [[ ! -z "$p_name" ]] && [[ ! $file_line =~ "$p_name" ]]; then
continue
else
break
fi
fi
fi
done < $file_content
You could use the same [[ ]] style checking for the two strings you're looking for:
if [[ $file_line != *"#"* ]] && [[ $file_line == *"$f_name"* ]] && [[ $file_line == *"$p_name"* ]]; then
...
fi
I removed the empty string check since an empty line won't contain $f_name and $p_name anyways.
If you expect sam will always come after abc_default_flow then you could combine the two checks into a single test:
if [[ $file_line != *"#"* ]] && [[ $file_line == *"$f_name"*"$p_name"* ]]; then
...
fi
If we look at the script as a whole, it'd be nice to get away from the explicit line-by-line loop. Scripts are more idiomatic when they chain together tools that process entire files. Something like:
sed -r '/^\s*#/d' my.txt | grep "$f_name" | grep "$p_name"

Find out which of the multiple conditions is true

I have a script that will check for multiple conditions in if statement and run a desired command if its true.
if [ ! -f /tmp/a ] && [ ! -f /tmp/b ]; then
touch /tmp/c else
echo "file exists" fi
I would now need to know which out of the multiple condition was true.
Eg: /tmp/a or /tmp/b which ever existed. Is there a way to get that in my else condition?
Since your if is using a compound condition, there is no way for else to figure out which part of the compound condition failed. You could rewrite your code this way:
a_exists=0
b_exists=0
[[ -f /tmp/a ]] && a_exists=1 # flag set to 1 if /tmp/a exists
[[ -f /tmp/b ]] && b_exists=1 # flag set to 1 if /tmp/b exists
if [[ $a_exists == 0 && $b_exists == 0 ]]; then
touch /tmp/c
else
[[ $a_exists == 1 ]] && echo "a exists"
[[ $b_exists == 1 ]] && echo "b exists"
fi
The above code can be written even more concisely with the Bash arithmetic operator (( ... )):
a_exists=0
b_exists=0
[[ -f /tmp/a ]] && a_exists=1 # flag set to 1 if /tmp/a exists
[[ -f /tmp/b ]] && b_exists=1 # flag set to 1 if /tmp/b exists
if !((a_exists + b_exists)); then
touch /tmp/c
else
((a_exists)) && echo "a exists"
((b_exists)) && echo "b exists"
fi
This smells like one day there will be more than two files to check. Use a loop:
i_am_happy=yeah
for f in a b
do
if [[ ! -f /tmp/$f ]]
then
echo "Criminy! No $f in tmp!" # or what else you would like to do.
i_am_happy=nope
fi
done
[[ i_am_happy == nope ]] && touch /tmp/c

Bash Boolean testing

I am attempting to run a block of code if one flag is set to true and the other is set to false. ie
var1=true
var2=false
if [[ $var1 && ! $var2 ]]; then var2="something"; fi
Since that did not evaluate the way that I expected I wrote several other test cases and I am having a hard time understanding how they are being evaluated.
aa=true
bb=false
cc="python"
if [[ "$aa" ]]; then echo "Test0" ; fi
if [[ "$bb" ]]; then echo "Test0.1" ; fi
if [[ !"$aa" ]]; then echo "Test0.2" ; fi
if [[ ! "$aa" ]]; then echo "Test0.3" ; fi
if [[ "$aa" && ! "$bb" ]]; then echo "Test1" ; fi
if [[ "$aa" && ! "$aa" ]]; then echo "Test2" ; fi
if [[ "$aa" ]] && ! [[ "$bb" ]]; then echo "test3" ; fi
if [[ "$aa" ]] && ! [[ "$cc" ]]; then echo "test4" ; fi
if [[ $aa && ! $bb ]]; then echo "Test5" ; fi
if [[ $aa && ! $aa ]]; then echo "Test6" ; fi
if [[ $aa ]] && ! [[ $bb ]]; then echo "test7" ; fi
if [[ $aa ]] && ! [[ $cc ]]; then echo "test8" ; fi
When I run the preceding codeblock the only output I get is
Test0
Test0.1
Test0.2
however, my expectation is that I would get
Test0
Test1
Test3
Test5
Test7
I have tried to understand the best way to run similar tests, however most examples I have found are set up in the format of
if [[ "$aa" == true ]];
which is not quite what I want to do. So my question is what is the best way to make comparisons like this, and why do several of the test cases that I would expect to pass simply not?
Thank you!
Without any operators, [[ only checks if the variable is empty. If it is, then it is considered false, otherwise it is considered true. The contents of the variables do not matter.
Your understanding of booleans in shell context is incorrect.
var1=true
var2=false
Both the above variables are true since those are non-empty strings.
You could instead make use of arithmetic context:
$ a=1
$ b=0
$ ((a==1 && b==0)) && echo y
y
$ ((a==0 && b==0)) && echo y
$
$ ((a && !(b))) && echo y; # This seems to be analogous to what you were attempting
y
The shell does not have Boolean variables, per se. However, there are commands named true and false whose exit statuses are 0 and 1, respectively, and so can be used similarly to Boolean values.
var1=true
var2=false
if $var1 && ! $var2; then var2="something"; fi
The difference is that instead of testing if var1 is set to a true value, you expand it to the name of a command, which runs and succeeds. Likewise, var2 is expanded to a command name which runs and fails, but because it is prefixed with ! the exit status is inverted to indicate success.
(Note that unlike most programming languages, an exit status of 0 indicates success because while most commands have 1 way to succeed, there are many different ways they could fail, so different non-zero values can be assigned different meanings.)
true and false are evaluated as strings ;)
[[ $var ]] is an equivalent of [[ -n $var ]] that check if $var is empty or not.
Then, no need to quote your variables inside [[. See this reminder.
Finally, here is an explication of the difference between && inside brackets and outside.
The closest you can come seems to be use functions instead of variables because you can use their return status in conditionals.
$ var1() { return 0; }
$ var2() { return 1; } # !0 = failure ~ false
and we can test this way
$ var1 && echo "it's true" || echo "it's false"
it's true
$ var2 && echo "it's true" || echo "it's false"
it's false
or this way
$ if var1; then echo "it's true"; else echo "it's false"; fi
it's true
$ if var2; then echo "it's true"; else echo "it's false"; fi
it's false
Hope this helps.

How can I do a logical test involving the return value of a piped command while taking advantage of short-circuiting?

Here is the code I'm trying to get right, but I know that it is logically incorrect, as the ! binds only to the first test.
# if Vim was compiled in the same month, skip.
if ! [[ -f /usr/local/bin/vim ]] && /usr/local/bin/vim --version | grep "compiled $(date +%b) [0-9]{1,2} $(date +%Y) " -
The reason that I know it is logically wrong is:
$ if ! [[ a == b ]] && [[ c == c ]]; then echo yy; fi
yy
$ if ! [[ a == a ]] && [[ a == c ]]; then echo yy; fi
$
I believe the solution is to use the curlies:
$ if ! { [[ a == a ]] && [[ a == c ]]; }; then echo yy; fi
yy
I wonder if there is anything more elegant.
if [[ a != a ]] || [[ a != c ]]; then echo yy; fi
or:
if ! /usr/local/bin/vim --version 2> /dev/null | grep "compiled ...

Repetitive Sequence in Bash

I'm stuck in something in my bash script, I have a string that composes of a repetitive sequence of 20s, e.g. 202020, there might be more or less 20s, e.g. 2020 or 2020202020, I want to create an if condition that if finds any strange number inside, e.g. 30 in 20203020, gives an error.
Any ideas?
this should do the test:
[[ "$var" =~ "^(20)+$" ]]
check this:
kent$ [[ "202" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20203" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20202002" =~ "^(20)+$" ]] && echo "y" || echo "n"
n
kent$ [[ "20202020" =~ "^(20)+$" ]] && echo "y" || echo "n"
y
how about this example, can you use it?
if [ "`echo "202020302020" | sed -e 's/20//g'`" ];
then echo "there is something";
fi
Extended patterns are a tiny bit more compact than regular expressions.
shopt -s extglob
if [[ $str = +(20) ]]; then
echo "twenties"
else
echo "mismatch"
fi
At some point, bash changed to treat all patterns in [[...]] expressions as extended patterns, so the shopt command may not be necessary.

Resources