What is the difference between [ test ] and [[ test ]] in bash?
When is one more appropriate than the other and what does the ; at the end do?
if [[ -z $DIRECTORY ]];
then
DIRECTORY=html
fi
if [ ! -d "$DIRECTORY" ]; then
echo installation directory "'${DIRECTORY}'" does not exist
exit 1
fi
[[ is a bash keyword similar to (but more powerful than) the [ command. See http://mywiki.wooledge.org/BashFAQ/031 and http://mywiki.wooledge.org/BashGuide/TestsAndConditionals Unless you're writing for POSIX sh, we recommend [[.
We usually use single square brackets when we:
Check something with files and want to use patterns (e.g. asterisk): if [ -L $file ]; then
Check artithmetic expressions: if [ $a -lt $b ]; then
Check something with strings and want to use " " and treat special characters as normal (e.g. asterisk): if [ -z "$string" ]; then
We usually double square brackets when we:
Want to use pattern with string (e.g. asterisk): if [[ "$string1" == *[sS]tring* ]]; then
Block patterns in file names (e.g. asterisk) e.g. we search file named *.sh: if [[ -a *.sh ]]; then
Want to use operators && and ||: if [[ $a == 3 || $b == 4]]; then
Don't want to put strings in " "
[ is for shell, [[ is for bash.
For example :
Try [ $A -eq 1 ]: if $A is not set, it raise an error.
[[ $A -eq 1 ]] will works.
Related
How do I compare a variable to a string (and do something if they match)?
Using variables in if statements
if [ "$x" = "valid" ]; then
echo "x has the value 'valid'"
fi
If you want to do something when they don't match, replace = with !=. You can read more about string operations and arithmetic operations in their respective documentation.
Why do we use quotes around $x?
You want the quotes around $x, because if it is empty, your Bash script encounters a syntax error as seen below:
if [ = "valid" ]; then
Non-standard use of == operator
Note that Bash allows == to be used for equality with [, but this is not standard.
Use either the first case wherein the quotes around $x are optional:
if [[ "$x" == "valid" ]]; then
or use the second case:
if [ "$x" = "valid" ]; then
Or, if you don't need an else clause:
[ "$x" == "valid" ] && echo "x has the value 'valid'"
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
Spaces between if and [ and ] are important
> and < are redirection operators so escape it with \> and \< respectively for strings.
To compare strings with wildcards, use:
if [[ "$stringA" == *"$stringB"* ]]; then
# Do something here
else
# Do something here
fi
I have to disagree one of the comments in one point:
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
No, that is not a crazy oneliner
It's just it looks like one to, hmm, the uninitiated...
It uses common patterns as a language, in a way;
And after you learned the language.
Actually, it's nice to read
It is a simple logical expression, with one special part: lazy evaluation of the logic operators.
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
Each part is a logical expression; the first may be true or false, the other two are always true.
(
[ "$x" == "valid" ]
&&
echo "valid"
)
||
echo "invalid"
Now, when it is evaluated, the first is checked. If it is false, than the second operand of the logic and && after it is not relevant. The first is not true, so it can not be the first and the second be true, anyway.
Now, in this case is the the first side of the logic or || false, but it could be true if the other side - the third part - is true.
So the third part will be evaluated - mainly writing the message as a side effect. (It has the result 0 for true, which we do not use here)
The other cases are similar, but simpler - and - I promise! are - can be - easy to read!
(I don't have one, but I think being a UNIX veteran with grey beard helps a lot with this.)
The following script reads from a file named "testonthis" line by line and then compares each line with a simple string, a string with special characters and a regular expression. If it doesn't match, then the script will print the line, otherwise not.
Space in Bash is so much important. So the following will work:
[ "$LINE" != "table_name" ]
But the following won't:
["$LINE" != "table_name"]
So please use as is:
cat testonthis | while read LINE
do
if [ "$LINE" != "table_name" ] && [ "$LINE" != "--------------------------------" ] && [[ "$LINE" =~ [^[:space:]] ]] && [[ "$LINE" != SQL* ]]; then
echo $LINE
fi
done
You can also use use case/esac:
case "$string" in
"$pattern" ) echo "found";;
esac
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples in Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Enjoy.
I would probably use regexp matches if the input has only a few valid entries. E.g. only the "start" and "stop" are valid actions.
if [[ "${ACTION,,}" =~ ^(start|stop)$ ]]; then
echo "valid action"
fi
Note that I lowercase the variable $ACTION by using the double comma's. Also note that this won't work on too aged bash versions out there.
I did it in this way that is compatible with Bash and Dash (sh):
testOutput="my test"
pattern="my"
case $testOutput in (*"$pattern"*)
echo "if there is a match"
exit 1
;;
(*)
! echo there is no coincidence!
;;esac
I was struggling with the same situation for a while, here is how I could resolve:
if [ "$var1" == "$var2" ]; then
#dowhateveryouwant
fi
Be careful with the spaces left before and after the comparison sign, otherwise it won't work or it'll give you an unexpected result.
I've spent so much time on using a single equal(=) sign but didn't work. I Hope it can help.
Are you having comparison problems? (like below?)
var="true"
if [[ $var == "true" ]]; then
# It should be working, but it is not...
else
# It is falling here...
fi
Try like the =~ operator (regular expression operator) and it might work:
var="true"
if [[ $var =~ "true" ]];then
# Now it works here!!
else
# No more inequality
fi
Bash regex operator =~ (official reference)
StackOverflow further examples (here)
How do I compare a variable to a string (and do something if they match)?
Using variables in if statements
if [ "$x" = "valid" ]; then
echo "x has the value 'valid'"
fi
If you want to do something when they don't match, replace = with !=. You can read more about string operations and arithmetic operations in their respective documentation.
Why do we use quotes around $x?
You want the quotes around $x, because if it is empty, your Bash script encounters a syntax error as seen below:
if [ = "valid" ]; then
Non-standard use of == operator
Note that Bash allows == to be used for equality with [, but this is not standard.
Use either the first case wherein the quotes around $x are optional:
if [[ "$x" == "valid" ]]; then
or use the second case:
if [ "$x" = "valid" ]; then
Or, if you don't need an else clause:
[ "$x" == "valid" ] && echo "x has the value 'valid'"
a="abc"
b="def"
# Equality Comparison
if [ "$a" == "$b" ]; then
echo "Strings match"
else
echo "Strings don't match"
fi
# Lexicographic (greater than, less than) comparison.
if [ "$a" \< "$b" ]; then
echo "$a is lexicographically smaller then $b"
elif [ "$a" \> "$b" ]; then
echo "$b is lexicographically smaller than $a"
else
echo "Strings are equal"
fi
Notes:
Spaces between if and [ and ] are important
> and < are redirection operators so escape it with \> and \< respectively for strings.
To compare strings with wildcards, use:
if [[ "$stringA" == *"$stringB"* ]]; then
# Do something here
else
# Do something here
fi
I have to disagree one of the comments in one point:
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
No, that is not a crazy oneliner
It's just it looks like one to, hmm, the uninitiated...
It uses common patterns as a language, in a way;
And after you learned the language.
Actually, it's nice to read
It is a simple logical expression, with one special part: lazy evaluation of the logic operators.
[ "$x" == "valid" ] && echo "valid" || echo "invalid"
Each part is a logical expression; the first may be true or false, the other two are always true.
(
[ "$x" == "valid" ]
&&
echo "valid"
)
||
echo "invalid"
Now, when it is evaluated, the first is checked. If it is false, than the second operand of the logic and && after it is not relevant. The first is not true, so it can not be the first and the second be true, anyway.
Now, in this case is the the first side of the logic or || false, but it could be true if the other side - the third part - is true.
So the third part will be evaluated - mainly writing the message as a side effect. (It has the result 0 for true, which we do not use here)
The other cases are similar, but simpler - and - I promise! are - can be - easy to read!
(I don't have one, but I think being a UNIX veteran with grey beard helps a lot with this.)
The following script reads from a file named "testonthis" line by line and then compares each line with a simple string, a string with special characters and a regular expression. If it doesn't match, then the script will print the line, otherwise not.
Space in Bash is so much important. So the following will work:
[ "$LINE" != "table_name" ]
But the following won't:
["$LINE" != "table_name"]
So please use as is:
cat testonthis | while read LINE
do
if [ "$LINE" != "table_name" ] && [ "$LINE" != "--------------------------------" ] && [[ "$LINE" =~ [^[:space:]] ]] && [[ "$LINE" != SQL* ]]; then
echo $LINE
fi
done
You can also use use case/esac:
case "$string" in
"$pattern" ) echo "found";;
esac
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples in Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Enjoy.
I would probably use regexp matches if the input has only a few valid entries. E.g. only the "start" and "stop" are valid actions.
if [[ "${ACTION,,}" =~ ^(start|stop)$ ]]; then
echo "valid action"
fi
Note that I lowercase the variable $ACTION by using the double comma's. Also note that this won't work on too aged bash versions out there.
I did it in this way that is compatible with Bash and Dash (sh):
testOutput="my test"
pattern="my"
case $testOutput in (*"$pattern"*)
echo "if there is a match"
exit 1
;;
(*)
! echo there is no coincidence!
;;esac
I was struggling with the same situation for a while, here is how I could resolve:
if [ "$var1" == "$var2" ]; then
#dowhateveryouwant
fi
Be careful with the spaces left before and after the comparison sign, otherwise it won't work or it'll give you an unexpected result.
I've spent so much time on using a single equal(=) sign but didn't work. I Hope it can help.
Are you having comparison problems? (like below?)
var="true"
if [[ $var == "true" ]]; then
# It should be working, but it is not...
else
# It is falling here...
fi
Try like the =~ operator (regular expression operator) and it might work:
var="true"
if [[ $var =~ "true" ]];then
# Now it works here!!
else
# No more inequality
fi
Bash regex operator =~ (official reference)
StackOverflow further examples (here)
I would like to recreate something like this
if ( arg1 || arg2 || arg 3) {}
And I did got so far, but I get the following error:
line 11: [.: command not found
if [ $char == $';' -o $char == $'\\' -o $char == $'\'' ]
then ...
I tried different ways, but none seemed to work. Some of the ones I tried.
For Bash, you can use the [[ ]] form rather than [ ], which allows && and || internally:
if [[ foo || bar || baz ]] ; then
...
fi
Otherwise, you can use the usual Boolean logic operators externally:
[ foo ] || [ bar ] || [ baz ]
...or use operators specific to the test command (though modern versions of the POSIX specification describe this XSI extension as deprecated -- see the APPLICATION USAGE section):
[ foo -o bar -o baz ]
...which is a differently written form of the following, which is similarly deprecated:
test foo -o bar -o baz
Bash's [[ ]] and (( )) are more powerful and flexible than [ ].
[[ ]] is for strings and (( )) is for integer logic and arithmetic
&& and || operators can be used inside [[ ]] and (( )), and () can be used for grouping
No need to quote variable expansions inside [[ ]] or (( )) - Bash doesn't do word splitting or globbing in these contexts
Inside (( )), there is no need for a $ behind variable names to expand them
[[ ]] and (( )) can span multiple lines, without the need for a line continuation with \
Using these, we can write clean, readable, and more reliable code.
Examples
Compound statements with integers
a=1 b=2 c=3
((a == 2 || (b == 2 && c == 3))) && echo yes # yields yes
Compound statements with strings
x=apple y=mango z=pear
[[ $x == orange || ($y == mango && $z == pear) ]] && echo yes # yields yes
[ equivalents for the above statements, using {}
[ "$a" -eq 2 ] || { [ "$b" -eq 2 ] && [ "$c" -eq 3 ]; }
[ "$x" == orange ] || { [ $y == mango ] && [ "$z" == pear ]; }
Related
Is double square brackets [[ ]] preferable over single square brackets [ ] in Bash?
How to use double or single brackets, parentheses, curly braces
Comparing integers: arithmetic expression or conditional expression on Unix & Linux
Test for non-zero length string in Bash: [ -n “$var” ] or [ “$var” ]
Conditional Expressions - Bash Manual
Charles' answer is correct in that it shows you how to do logical operations on commands within (and without, for that matter) an if statement, but it looks more like you want to use case here:
case $char in
\;|\\|\') echo found;;
*) echo 'not found';;
esac
In bash, to group conditionals using the [ ] construct you must surround each group using parenthesis. Each opening/closing parenthesis must be escaped and preceded/succeeded with a white space. See below:
if [ \( "$char" = "$;" \) -o \( "$char" = "$\\" \) -o \( "$char" = "$\" \) ]
As such, it's definitely best to follow everyone elses advice and use bash's newer [[ ]] construct. Lastly, as I understand it == is a relational operator intended to be used with-in arithmetic expressions. i.e. -
$((3==4))
Cameron Newham and Bill Rosenblatt, "Learning the bash Shell" Jan.1998
Would someone please care to explain how on earth this is supposed to make sense:
Contents of insanity.sh:
#!/bin/bash
ARG=""
if [ -n $ARG ]; then
echo string is greater than zero
fi
if [ -z $ARG ]; then
echo string is empty
fi
Running the script:
[USERNAME#login001 clusterUtils]$ ./insanity.sh
string is greater than zero
string is empty
Currently using this tutorial at the moment.
It is happening because you're not quoting $ARG inside [ ... ].
Without quoting your code is effectively running as:
if [ -n ]; then
echo string is greater than zero
fi
if [ -z ]; then
echo string is empty
fi
Any non-empty string between [ ... ] will evaluate to true, hence both if conditions are successful.
Fix: It is recommended to use [[ ... ]] as you're using bash:
arg=""
if [[ -n $arg ]]; then
echo 'string is greater than zero'
fi
if [[ -z $arg ]]; then
echo 'string is equal to zero, empty'
fi
[[ ... ]] doesn't require your variables to be quoted like [ ... ] sine [ is an external command and [[ ... ]] is a builtin bash construct.
Also avoid all uppercase variables in your script to avoid conflicts with reserved env variables.
I'm trying to write a short bash script that optionally accepts arguments from the command line, or prompts for their input
if [ [ -z "$message" ] && [ -z "$predefined" ] ] ; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" ]; then
if [ -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
fi
If neither message nor predefined has been passed in as command line arguments, then the code should prompt for a value for message; otherwise if predefined has been passed in as a command line argument, then the script should test for the existence of a file of that name and only continue if the file does exist
But I'm getting the following error
[: -z: binary operator expected
at the first of those if tests
Any help in explaining what's wrong with my syntax for that first if statement? Or providing an alternative syntax to achieve the same objectives.
The first if is not well-formed. This would work:
if [ -z "$message" ] && [ -z "$predefined" ]; then
or this:
if test -z "$message" && test -z "$predefined"; then
or this bash-specific, easy but dirty way:
if [[ -z "$message" ]] && [[ -z "$predefined" ]]; then
or this bash-specific proper way:
if [[ -z $message && -z $predefined ]]; then
In this last version the double-quotes are unnecessary, not a typo.
Thanks #mklement0 for the corrections in the bash-specific style, and for this final comment:
I should note that there's one case where double-quoting is still a must inside [[ ... ]], namely if you want a variable reference on the right side of a string comparison (==) to be treated as a literal:
v='[a]'
[[ $v == $v ]] # FALSE!
[[ $v == "$v" ]] # true
Without double-quoting, the right-hand side is interpreted as a pattern. Some people advocate always double-quoting variable references so as not to have to remember such subtleties. That said (from bash 3.2 on), you must NOT double-quote the right operand when regex matching with =~
test expression1 -a expression2
is true if both expressions are true.
test expression1 -o expression2
is true if either or both expressions are true.
if [ -z "$message" -a -z "$predefined" ]; then
read -p "Enter message [$defaultMessage]: " message
message=${message:-$defaultMessage}
else
if [ -n "$predefined" -a -f $base/$environment/vle/data/$predefined.txt ]; then
echo Predefined message file $predefined.txt does not exist
exit 1
fi
fi
This was able to combine 4 test into 2 while also getting rid of one nested if expression; then ; fi