ksh if loop vs [[ ]] && { cmd1 ; } || { cmd2 ; } - if-statement

I am working on a shell script and following syntax works fine:
if [[ 1 -eq 1 ]] ; then
[[ 2 -eq 1 ]]
else
echo hi
fi
Debug run :
+ [[ 1 -eq 1 ]]
+ [[ 2 -eq 1 ]]
If i shrink the statement it jumps to the else condition:
**[[ 1 -eq 1 ]] && { [[ 2 -eq 1 ]] ; } || echo hi**
Debug run:
+ [[ 1 -eq 1 ]]
+ [[ 2 -eq 1 ]]
+ echo hi
hi
any ideas what is missing?
Thanks much.
Sam

You could say the following:
[[ 1 -eq 1 ]] && { [[ 2 -eq 1 ]] ; true ; } || echo hi
if the first test passes, you go into the {...} block and once you run, what you wanted to run in there, by ending with true you do not continue past || here standing in for your else clause.
The problem is. [[ 2 -eq 1 ]] returned false and hence you get to your echo hi beyond ||.
All that said, you may see it looks a bit ugly and in this case (esp. as multiple conditions are involved which may impact how your pipeline gets evaluated). Well, I would recommend not saving on few newlines and for sake of obvious and readable code use the longer form.

Related

How to structure a compound conditional with several tests (at least 1 of which is regex)

I searched for this but haven't found an answer to this particular situation. I'm familiar with file tests in shells and with using the [[ ]] syntax to perform regex matching.
Is there a way to combine these two operations in a compound conditional that doesn't require multiple nested ifs?
So far I've tried the following (...and other much crazier variations):
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes ; fi
if [ -e ~/.profile -a ( $0 =~ bash ) ]; then echo yes ; fi
if [ -e ~/.profile -a [ $0 =~ bash ] ]; then echo yes ; fi
if [[ -e ~/.profile -a $0 =~ bash ]]; then echo yes ; fi
if [[ ( -e ~/.profile ) -a ( $0 =~ bash ) ]]; then echo yes ; fi
if [ -e ~/.profile -a $0 =~ bash ]; then echo yes; fi
if [ -e ~/.profile -a $( [ $0 =~ bash ] ) ]; then echo yes; fi
if [ -e ~/.profile -a [[ $0 =~ bash ]] ]; then echo yes; fi
if [ -e ~/.profile -a $([[ $0 =~ bash ]]) ]; then echo yes; fi
-a is treated as an AND when using single brackets, eg:
$ [ 3 -gt 1 -a 2 -lt 3 ] && echo 'true'
true
For double brackets you want to use &&, eg:
$ [[ 3 -gt 1 && 2 -lt 3 ]] && echo 'true'
true
Alternatively you can && to separate tests regardless of whether you're using single or double brackets, eg:
$ [ 3 -gt 1 ] && [ 2 -lt 3 ] && echo 'true'
true
$ [[ 3 -gt 1 ]] && [[ 2 -lt 3 ]] && echo 'true'
true
$ [ 3 -gt 1 ] && [[ 2 -lt 3 ]] && echo 'true'
true
NOTE: same rules apply for -o vs || (aka OR)
Apparently, when you want to represent a LOGICAL AND between these two statements, you must use && instead of -a (which the shell interprets as "does this file exist" file test in double brackets). Also, for the regex to work, the statement must be within [[ ]]. What was unknown to me at the time is that even though -a changes its meaning in double brackets, the -e -w -r and other file tests don't change their functionality (e.g. it's the same for single or double brackets).
if [[ -w ~/.bash_profile && $0 =~ bash ]]; then ( echo 1 ; echo 2 ) >> .bash_profile
elif [[ -w ~/.profile && <someothercondition> ]]; then
( echo 3
echo 4
echo 5
) >> .profile
fi

Bash comparison check to accept values only with format 0.1

I have this snippet:
if [[ $1 =~ ^[+-]?[0-9]+\.?[0-9]*$ ]]; then
echo 'version is good'
exit 0
else
exit 1
fi
The problem is that, the snippet $1 =~ ^[+-]?[0-9]+\.?[0-9]*$ should only validate versions formatted as number.number
Currently this chunk of code validates inputs as
1
01
0.1
Is there any way of making the code to only accept inputs formatted as 0.1 / 0.3.2 / 0.1.141 etc.
Thanks in advance.
EDIT:
To clarify this question, the code should only accept numbers separated with dots, like software program versioning.
I suggest this regex: ^[0-9]+(\.[0-9]+){1,2}$
I propose without regex :
[[ "0" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # Nothing
[[ "01" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # Nothing
[[ "0.1" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "0.3.2" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "0.1.141" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "10.1.141" == ?([+-])+([0-9]).+([0-9.]) ]] && echo OK # OK
To clarify this question, the code should only accept numbers
separated with dots, like software program versioning.
[[ "0" == +([0-9]).+([0-9.]) ]] && echo OK # Nothing
[[ "01" == +([0-9]).+([0-9.]) ]] && echo OK # Nothing
[[ "0.1" == +([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "0.3.2" == +([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "0.1.141" == +([0-9]).+([0-9.]) ]] && echo OK # OK
[[ "10.1.141" == +([0-9]).+([0-9.]) ]] && echo OK # OK

Issue with RegEx in bash?

I have the following RegEx written to match any no of repeating patterns. It is working in https://regex101.com/ when tested online. But, it is not working when used in Linux Bash. Please help!!
pair_format="^([[:blank:]]*\[[[:blank:]]*[^=[\]]+[[:blank:]]*=[[:blank:]]*[^=[\]]+[[:blank:]]*\][[:blank:]]*)+$"
Sample data to test:
CUSTOM_ARGS_KV="[X=Y][A=B][C=D][FASLFJSDLF=9]"
if [[ ! $CUSTOM_ARGS_KV =~ $pair_format ]]; then; echo "invalid!!!!"; else echo "valid"; fi
Here is my script:
CUSTOM_ARGS_KV='[X=Y][A=B][C=D][FASLFJSDLF=9]' #example input
if [ ! -z "$CUSTOM_ARGS_KV" ]; then
pair_format="^([[:blank:]]*\[[[:blank:]]*[^=[\]]+[[:blank:]]*=[[:blank:]]*[^=[\]]+[[:blank:]]*\][[:blank:]]*)+$"
if [[ ! $CUSTOM_ARGS_KV =~ $pair_format ]]; then
echo "Error! CUSTOM_ARGS_KV is not according to format [key1=value1] [key2=value2] etc. Or either of key/value of a pair are kept blank"
exit 1
fi
fi
Not quoting the test works for me. Example :
pair_format="^([[:blank:]]*\[[[:blank:]]*[^=[\]]+[[:blank:]]*=[[:blank:]]*[^=[\]]+[[:blank:]]*\][[:blank:]]*)+$"
[[ ! "[X=Y][A=B][C=D][FASLFJSDLF=9]" =~ $pair_format ]] && echo "Match"
Output
Match
Regards!
Edit
Correcting your script here. This worked perfectly here :
CUSTOM_ARGS_KV='[X=Y][A=B][C=D][FASLFJSDLF=9]'
if [ ! -z "$CUSTOM_ARGS_KV" ];
then
pair_format='^([[:blank:]]*\[[[:blank:]]*[^=[\]]+[[:blank:]]*=[[:blank:]]*[^=[\]]+[[:blank:]]*\][[:blank:]]*)+$'
if [[ ! $CUSTOM_ARGS_KV =~ $pair_format ]]
then
echo 'Error! CUSTOM_ARGS_KV is not according to format [key1=value1] [key2=value2] etc. Or either of key/value of a pair are kept blank'
#exit 1
fi
fi
This regex should work for you:
pair_format="^(\[[^]=[]+=[^]=[]\])+$"
CUSTOM_ARGS_KV="[X=Y][A=B][C=D][FASLFJSDLF=9]"
[[ $CUSTOM_ARGS_KV =~ $pair_format ]] && echo "valid" || echo "invalid"
valid
It is very important to keep ] at first position in bracket expression after ^ and keep [ at last position.
PS: I have removed [[:blank:]]* fromn regex for sake of readability.
Code Demo
I ran your code and get the following error:
./testing: line 3: =[X=Y][A=B][C=D][FASLFJSDLF=9]: command not found
./testing: line 5: syntax error near unexpected token `;'
./testing: line 5: `if [[ ! "$CUSTOM_ARGS_KV" =~ "$pair_format" ]]; then; echo "invalid!!!!"; else echo "valid"; fi'
The line 3 problem was that I didn't take of the $ from CUSTOM_ARGS_KV.
Use the if like this:
if [[ ! "$CUSTOM_ARGS_KV" =~ "$pair_format" ]]
then
....echo "invalid!!!!"
else
....echo "valid"
fi

The equal tilde operator is not working in bash 4

In a server with bash version 3 I do this:
bash3$ e="tar xfz"; [[ "$e" =~ "^tar" ]] && echo 0 || echo 1
0
But when I execute the same command in bash version 4
bash4$ e="tar xfz"; [[ "$e" =~ "^tar" ]] && echo 0 || echo 1
1
I tried it in CentOS, Fedora and Ubuntu and got the same results. What is wrong?
Quoting the section on regular expressions from Greg's Wiki:
Before 3.2 it was safe to wrap your regex pattern in quotes but this has changed in 3.2. Since then, regex should always be unquoted.
This is the most compatible way of using =~:
e='tar xfz'
re='^tar'
[[ $e =~ $re ]] && echo 0 || echo 1
This should work on both versions of bash.
In this case, where you just want to make sure that your parameter starts with tar, you don't need regular expression matching, simple pattern matching works as well:
e='tar xfz'
[[ $e == tar* ]] && echo 0 || echo 1

Bash syntax errors: operand expected with arithmetic operations

I am constructing a script to count the occurrences of two letters in a given string. I cannot figure out how to make the variable a testable number.
#!/bin/bash
touch ~/trfindlog.txt ~/trfindt ~/trfindr
echo $1 > ~/trfindlog.txt
cat ~/trfindlog.txt | grep -oi r | wc -l > ~/trfindr
cat ~/trfindlog.txt | grep -oi t | wc -l > ~/trfindt
varR='/trfindr'
varT='/trfindt'
if [[ "${varR}" -eq 0 && "${varT}" -eq 0 ]]
then
echo "This phrase contains no Rs or Ts."
elif [[ "${varR}" -eq 1 && "${varT}" -eq 1 ]]
then
echo "This phrase contains 1 R and 1 T."
elif [[ "${varR}" -gt 1 && "${varT}" -eq 1 ]]
then
echo "This phrase contains ${varR} Rs and 1 T."
elif [[ "${varR}" -eq 1 && "${varT}" -gt 1 ]]
then
echo "This phrase contains 1 R and ${varT} Ts."
elif [[ "${varR}" -gt 1 && "${varT}" -gt 1 ]]
then
echo "This phrase contains ${varR} Rs and ${varT} Ts."
fi
rm ~/trfindlog.txt ~/trfindt ~/trfindr
exit
This script is giving me the following errors.
/automount/home/jcampbell/tools/itc/trfind.sh: line 12: [[: /trfindr: syntax error: operand expected (error token is "/trfindr")
/automount/home/jcampbell/tools/itc/trfind.sh: line 16: [[: /trfindr: syntax error: operand expected (error token is "/trfindr")
/automount/home/jcampbell/tools/itc/trfind.sh: line 20: [[: /trfindr: syntax error: operand expected (error token is "/trfindr")
/automount/home/jcampbell/tools/itc/trfind.sh: line 24: [[: /trfindr: syntax error: operand expected (error token is "/trfindr")
/automount/home/jcampbell/tools/itc/trfind.sh: line 28: [[: /trfindr: syntax error: operand expected (error token is "/trfindr")
Here is the working script. This is just for kicks & educating myself. I am glad to receive a variety of answers.
#!/bin/bash
touch ~/trfindlog.txt
echo $1 > ~/trfindlog.txt
varR=$(echo $1 | tr -cd r)
varT=$(echo $1 | tr -cd t)
if [[ "${#varR}" -eq 0 && "${#varT}" -eq 0 ]]
then
echo "This phrase contains no Rs or Ts."
elif [[ "${#varR}" -eq 1 && "${#varT}" -eq 1 ]]
then
echo "This phrase contains 1 R and 1 T."
elif [[ "${#varR}" -gt 1 && "${#varT}" -eq 1 ]]
then
echo "This phrase contains ${#varR} Rs and 1 T."
elif [[ "${#varR}" -eq 1 && "${#varT}" -gt 1 ]]
then
echo "This phrase contains 1 R and ${#varT} Ts."
elif [[ "${#varR}" -gt 1 && "${#varT}" -gt 1 ]]
then
echo "This phrase contains ${#varR} Rs and ${#varT} Ts."
fi
rm ~/trfindlog.txt
exit
Putting the variable in a file is cumbersome, inelegant, and, with a static filename, will break if you run two instances of the script at the same time. All of this would be a lot more succinct with the variable in memory.
With Bash, you can make a copy of the variable and perform a simple substitution.
Rs=${1//[!R]/}
Ts=${1//[!T]/}
Now, the length of each of these strings is the number of occurrences of the characters you were looking for.
echo "We have ${#Rs} R characters and ${#Ts} T characters."
Deciding whether to print a plural s should be a simple addition. Hint: If the first string is exactly R you want to suppress the s. But if you want flexible wording, it may be simpler to use a case statement over the possibilities.
case $Rs:$Ts in
:) echo "We have none of either";;
R:) echo "We have one R and no Ts.";;
:T) echo "We have no Rs and one T.";;
R:T) echo "We have one of each.";;
*:) echo "We have ${#Rs} Rs and no Ts.";;
*:T) echo "We have ${#Rs} Rs and one T.";;
:*) echo "We have no Rs and ${#Ts} Ts.";;
R:*) echo "We have one R and ${#Ts} Ts.";;
*:*) echo "We have ${#Rs} and ${#Ts} Ts.";;
esac
I would still be tempted to handle the cornermost cases : and R:T as above, and then generate a string from smaller pieces in the remaining cases.
You need to use something like
var=$(grep -c r ~/trfindlog.txt)
Note that grep will count the number of matching lines, and not the number of matches. So, what you really need is more something like:
var=$(echo $1 | tr -cd r)
echo ${#var}