Bash - when using Double Square brackets and when single square brackets [duplicate] - linux

This question already has answers here:
unary operator expected in shell script when comparing null value with string
(2 answers)
"unary operator expected" error in Bash if condition
(6 answers)
Closed 8 years ago.
Please follow the following example (please notice that STRING is null)
Get Error
[root#linux /tmp]# STRING=
[root#linux /tmp]# [ $STRING = dog ] && echo "yes its a dog"
bash: [: =: unary operator expected
Syntax without Error
[root#linux /tmp]# STRING=
[root#linux /tmp]# [[ $STRING = dog ]] && echo "yes its a dog"
[root#linux /tmp]# [[ $STRING != dog ]] && echo "yes its a dog"
yes its a dog
Why when I am using Single Square brackets I get - unary operator expected ?
But in case of Double Square brackets the syntax give good results

Unlike [[ ]], single square brackets are subject to word splitting like normal commands. Think of it similarly as passing arguments to test builtin. [[ ]] are special and variables within it are not split with IFS. This is why it's preferable to use [[ ]] since it's more efficient and safer.
This one is just one of the things you can't do with [. With [ you'd play around with { } and ( ) to make it work. Note that ( ) summons a subshell and { } would always need a semicolon in the end which is ugly. However you can do it safely with [[ ]]:
[[ a != b && c != d && ( something || something ) ]]
By the way to fix your problem you have to place the variable in double-quotes to prevent splitting:
[ "$STRING" = dog ]
With [[ it's not needed.
Sere Word Splitting: https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html
Add:
It seems like $STRING there was actually skipped as an argument for being empty instead of being split. Similarly the concept of it being interpreted as a normal command would apply and applying double quotes would fix it.

Because [[ is special. [ needs to retain Bourne shell compatibility, including its limitations. Since [[ is a construct introduced separately in bash, it can be fixed.

It's tough to search on, but this is very frequently asked. Try googling for "bash square brackets" and you will see lots of discussions on this topic, mostly on SO:
https://www.google.com/search?q=bash+square+brackets
https://serverfault.com/questions/52034/what-is-the-difference-between-double-and-single-square-brackets-in-bash
http://mywiki.wooledge.org/BashGuide/TestsAndConditionals#Conditional_Blocks_.28if.2C_test_and_.5B.5B.29
The short answer is to always use [[ unless you know otherwise...

Related

Command output to string comparison in Zsh [duplicate]

This question already has answers here:
How to compare strings in Bash
(12 answers)
Closed 4 years ago.
I'm trying to get an if statement to work in Bash (using Ubuntu):
#!/bin/bash
s1="hi"
s2="hi"
if ["$s1" == "$s2"]
then
echo match
fi
I've tried various forms of the if statement, using [["$s1" == "$s2"]], with and without quotes, using =, == and -eq, but I still get the following error:
[hi: command not found
I've looked at various sites and tutorials and copied those, but it doesn't work - what am I doing wrong?
Eventually, I want to say if $s1 contains $s2, so how can I do that?
I did just work out the spaces bit... :/ How do I say contains?
I tried
if [[ "$s1" == "*$s2*" ]]
but it didn't work.
For string equality comparison, use:
if [[ "$s1" == "$s2" ]]
For string does NOT equal comparison, use:
if [[ "$s1" != "$s2" ]]
For the a contains b, use:
if [[ $s1 == *"$s2"* ]]
(and make sure to add spaces between the symbols):
Bad:
if [["$s1" == "$s2"]]
Good:
if [[ "$s1" == "$s2" ]]
You should be careful to leave a space between the sign of '[' and double quotes where the variable contains this:
if [ "$s1" == "$s2" ]; then
# ^ ^ ^ ^
echo match
fi
The ^s show the blank spaces you need to leave.
You need spaces:
if [ "$s1" == "$s2" ]
I suggest this one:
if [ "$a" = "$b" ]
Notice the white space between the openning/closing brackets and the variables and also the white spaces wrapping the '=' sign.
Also, be careful of your script header. It's not the same thing whether you use
#!/bin/bash
or
#!/bin/sh
Here's the source.
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash IMO.
Here are some examples 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
This question has already great answers, but here it appears that there is a slight confusion between using single equal (=) and double equals (==) in
if [ "$s1" == "$s2" ]
The main difference lies in which scripting language you are using. If you are using Bash then include #!/bin/bash in the starting of the script and save your script as filename.bash. To execute, use bash filename.bash - then you have to use ==.
If you are using sh then use #!/bin/sh and save your script as filename.sh. To execute use sh filename.sh - then you have to use single =. Avoid intermixing them.
I would suggest:
#!/bin/bash
s1="hi"
s2="hi"
if [ $s1 = $s2 ]
then
echo match
fi
Without the double quotes and with only one equals.
$ if [ "$s1" == "$s2" ]; then echo match; fi
match
$ test "s1" = "s2" ;echo match
match
$
I don't have access to a Linux box right now, but [ is actually a program (and a Bash builtin), so I think you have to put a space between [ and the first parameter.
Also note that the string equality operator seems to be a single =.
This is more a clarification than an answer! Yes, the clue is in the error message:
[hi: command not found
which shows you that your "hi" has been concatenated to the "[".
Unlike in more traditional programming languages, in Bash, "[" is a command just like the more obvious "ls", etc. - it's not treated specially just because it's a symbol, hence the "[" and the (substituted) "$s1" which are immediately next to each other in your question, are joined (as is correct for Bash), and it then tries to find a command in that position: [hi - which is unknown to Bash.
In C and some other languages, the "[" would be seen as a different "character class" and would be disjoint from the following "hi".
Hence you require a space after the opening "[".
Use:
#!/bin/bash
s1="hi"
s2="hi"
if [ "x$s1" == "x$s2" ]
then
echo match
fi
Adding an additional string inside makes it more safe.
You could also use another notation for single-line commands:
[ "x$s1" == "x$s2" ] && echo match
For a version with pure Bash and without test, but really ugly, try:
if ( exit "${s1/*$s2*/0}" )2>/dev/null
then
echo match
fi
Explanation: In ( )an extra subshell is opened. It exits with 0 if there was a match, and it tries to exit with $s1 if there was no match which raises an error (ugly). This error is directed to /dev/null.

checking if a string is contained in other string in shell

if I run this, it doesnt print yes.
if [s == [Ss]]
then echo "yes"
fi
However, if I write double [], so will be like
if [[s == [Ss]]]
then echo "yes"
fi
it prints yes
I wonder WHY
Continuing from my comment, POSIX-shell does not allow the use of character classes (e.g. [...]) within test (synonymous with [...]). Only bash [[ ... ]] allows character classes within it.
If you have coreutils installed (about every distro does) you can still match a character list against the string returning the index (1-based) within the string of the first character in the character list. For your case you could use:
if [ $(expr index "s" "sS") -gt '0' ]; then
echo yes;
fi
Where expr index "s" "sS" is the usual form of index string charlist, which requires the use of expr before it.
If you are limited to POSIX shell, then you can sill use expr string : regex to match against a regular expression.
if [ $(expr "s" : '[sS]$') -gt '0' ]; then
echo yes;
fi
(note: the regex must match the entire contents of the string being tested against)
When comparing strings, the double-bracket syntax features shell globbing. As you are trying to compare with both the upper case and lower case.
Also the double bracket syntax helps in Regex pattern matching.
The condition you gave is similar to
if [[ "$stringvar" == *string* ]]; then
This means that an asterisk (“*”) will expand to literally anything, just as you probably know from normal command-line usage. Therefore, if $stringvar contains the phrase “string” anywhere, the condition will return true. Other forms of shell globbing are allowed, too. If you’d like to match both “String” and “string”, you could use the following syntax:
if [[ "$stringvar" == *[sS]tring* ]]; then
[ doesn't support pattern-matching. Use case instead.
inp=s
case $inp in
[sS]) echo "Matched s/S" ;;
*) echo "Did not match s/S" ;;
esac

How to check if package is installed, then ask if user wants to install package? [duplicate]

This question already has answers here:
How to compare strings in Bash
(12 answers)
Closed 4 years ago.
I'm trying to get an if statement to work in Bash (using Ubuntu):
#!/bin/bash
s1="hi"
s2="hi"
if ["$s1" == "$s2"]
then
echo match
fi
I've tried various forms of the if statement, using [["$s1" == "$s2"]], with and without quotes, using =, == and -eq, but I still get the following error:
[hi: command not found
I've looked at various sites and tutorials and copied those, but it doesn't work - what am I doing wrong?
Eventually, I want to say if $s1 contains $s2, so how can I do that?
I did just work out the spaces bit... :/ How do I say contains?
I tried
if [[ "$s1" == "*$s2*" ]]
but it didn't work.
For string equality comparison, use:
if [[ "$s1" == "$s2" ]]
For string does NOT equal comparison, use:
if [[ "$s1" != "$s2" ]]
For the a contains b, use:
if [[ $s1 == *"$s2"* ]]
(and make sure to add spaces between the symbols):
Bad:
if [["$s1" == "$s2"]]
Good:
if [[ "$s1" == "$s2" ]]
You should be careful to leave a space between the sign of '[' and double quotes where the variable contains this:
if [ "$s1" == "$s2" ]; then
# ^ ^ ^ ^
echo match
fi
The ^s show the blank spaces you need to leave.
You need spaces:
if [ "$s1" == "$s2" ]
I suggest this one:
if [ "$a" = "$b" ]
Notice the white space between the openning/closing brackets and the variables and also the white spaces wrapping the '=' sign.
Also, be careful of your script header. It's not the same thing whether you use
#!/bin/bash
or
#!/bin/sh
Here's the source.
Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash IMO.
Here are some examples 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
This question has already great answers, but here it appears that there is a slight confusion between using single equal (=) and double equals (==) in
if [ "$s1" == "$s2" ]
The main difference lies in which scripting language you are using. If you are using Bash then include #!/bin/bash in the starting of the script and save your script as filename.bash. To execute, use bash filename.bash - then you have to use ==.
If you are using sh then use #!/bin/sh and save your script as filename.sh. To execute use sh filename.sh - then you have to use single =. Avoid intermixing them.
I would suggest:
#!/bin/bash
s1="hi"
s2="hi"
if [ $s1 = $s2 ]
then
echo match
fi
Without the double quotes and with only one equals.
$ if [ "$s1" == "$s2" ]; then echo match; fi
match
$ test "s1" = "s2" ;echo match
match
$
I don't have access to a Linux box right now, but [ is actually a program (and a Bash builtin), so I think you have to put a space between [ and the first parameter.
Also note that the string equality operator seems to be a single =.
This is more a clarification than an answer! Yes, the clue is in the error message:
[hi: command not found
which shows you that your "hi" has been concatenated to the "[".
Unlike in more traditional programming languages, in Bash, "[" is a command just like the more obvious "ls", etc. - it's not treated specially just because it's a symbol, hence the "[" and the (substituted) "$s1" which are immediately next to each other in your question, are joined (as is correct for Bash), and it then tries to find a command in that position: [hi - which is unknown to Bash.
In C and some other languages, the "[" would be seen as a different "character class" and would be disjoint from the following "hi".
Hence you require a space after the opening "[".
Use:
#!/bin/bash
s1="hi"
s2="hi"
if [ "x$s1" == "x$s2" ]
then
echo match
fi
Adding an additional string inside makes it more safe.
You could also use another notation for single-line commands:
[ "x$s1" == "x$s2" ] && echo match
For a version with pure Bash and without test, but really ugly, try:
if ( exit "${s1/*$s2*/0}" )2>/dev/null
then
echo match
fi
Explanation: In ( )an extra subshell is opened. It exits with 0 if there was a match, and it tries to exit with $s1 if there was no match which raises an error (ugly). This error is directed to /dev/null.

BASH: how to pass in arguments to an alias: CANNOT USE A FUNCTION - syntax of Bash conditionals

This question differs in that the classic "use a function" answer WILL NOT work. Adding a note to an existing Alias question is equivalent to sending a suggestion e-mail to Yahoo.
I am trying to write macros to get around BASH's horrendous IF syntax. You know, the [, [[, ((...BASH: the PHP of flow control...just add another bracket. I'm still waiting for the "(((((((" form. Not quite sure why BASH didn't repurpose "(", as "(" has no real semantics at the if statement.
The idea is to have named aliases for [, [[ and (( , as each one of these durned test-ish functions has a frustratingly different syntax. I honestly can never remember which is which (how COULD you? It's completely ad hoc!), and good luck trying to google "[[".
I would then use the names as a mnemonic, and the alias to get rid of the completely awful differences in spacing requirements. Examples: "whatdoyoucallthisIf" for "((", "shif" (for shell if), "mysterydoublesquarebacketif" for that awful [[ thing which seems to mostly do the same thing as [, only it doesn't.
Thus, I MUST have something of the form:
alias IFREPLACEMENT="if [ \$# ]; then"
But obviously not \$#, which would just cement in the current argument list to the shell running the alias.
Functions will not work in this case, as the function:
function IFREPLACEMENT {
if [ $# ]; then
}
is illegal.
In CSH, you could say
alias abc blah blah !*
!1, etc. Is there ANYTHING in BASH that is similar (no, !* doesn't work in BASH)?
Or am [ "I just out of luck" ]; ?
As an aside, here are some of the frustrating differences involving test-ish functions in BASH that I am trying to avoid by using well-defined aliases that people would have to use instead of picking the wrong "[[", "[" or "((":
"((" is really creepy...if a variable contains the name of another variable, it's derferenced for as many levels as necessary)
"((" doesn't require a spaces like '[' and '[['
"((" doesn't require "$" for variables to be dereferenced
['s "-gt" is numeric or die. [[ seems to have arbitrary restrictions.
'[' and '[[' use ">" (etc) as LEXICAL comparison operators, but they have frustratingly different rules that make it LOOK like they're doing numeric comparisons when they really aren't.
for a variable: a="" (empty value), [ $a == 123 ] is a syntax error, but (( a == 123 )) isn't.
Sure, functions will work, but not like a macro:
function IFREPLACEMENT {
[[ "$#" ]]
}
IFREPLACEMENT "$x" = "$y" && {
echo "the same
}
FWIW, here's a brutal way to pass arguments to an alias.
$ alias enumerate='bash -c '\''for ((i=0; i<=$#; i++)); do arg=${!i}; echo $i $arg; done'\'
$ enumerate foo bar baz
0 foo
1 bar
2 baz
Clearly, because a new bash shell is spawned, whatever you do won't have any effect on the current shell.
Update: Based on feedback from #konsolebox, the recommendation is now to always use [[...]] for both simplicity and performance (the original answer recommended ((...)) for numerical/Boolean tests).
#Oliver Charlesworth, in a comment on the question, makes the case for not trying to hide the underlying bash syntax - and I agree.
You can simplify things with the following rules:
Always use [[ ... ]] for tests.
Only use [ ... ] if POSIX compatibility is a must. If available, [[ ... ]] is always the better choice (fewer surprises, more features, and almost twice as fast[1]).
Use double-quoted, $-prefixed variable references - for robustness and simplicity (you do pay a slight performance penalty for double-quoting, though1) - e.g., "$var"; see the exceptions re the RHS of == and =~ below.
Whitespace rules:
ALWAYS put a space after the initial delimiter and before the closing delimiter of conditionals (whether [[ / (( or ]] / )))
NEVER put spaces around = in variable assignments.
These rules are more restrictive than they need to be - in the interest of simplification.
Tips and pitfalls:
Note that for numeric comparison with [[ ... ]], you must use -eq, -gt, -ge, -lt, -le, because ==, <, <=, >, >= are for lexical comparison.
[[ 110 -gt 2 ]] && echo YES
If you want to use == with pattern matching (globbing), either specify the entire RHS as an unquoted string, or, at least leave the special globbing characters unquoted.
[[ 'abc' == 'a'* ]] && echo YES
Similarly, performing regex matching with =~ requires that either the entire RHS be unquoted, or at least leave the special regex chars. unquoted - if you use a variable to store the regex - as you may have to in order to avoid bugs with respect to \-prefixed constructs on Linux - reference that variable unquoted.
[[ 'abc' =~ ^'a'.+$ ]] && echo YES
re='^a.+$'; [[ 'abc' =~ $re ]] && echo YES # *unquoted* use of var. $re
An alternative to [[ ... ]], for purely numerical/Boolean tests, is to use arithmetic evaluation, ((...)), whose performance is comparable to [[ (about 15-20% slower1); arithmetic evaluation (see section ARITHMETIC EVALUATION in man bash):
Allows C-style arithmetic (integer) operations such as +, -, *, /, **, %, ...
Supports assignments, including increment and decrement operations (++ / --).
No $ prefix required for variable references.
Caveat: You still need the $ in 2 scenarios:
If you want to specify a number base or perform up-front parameter expansion, such as removing a prefix:
var=010; (( 10#$var > 9 )) && echo YES # mandate number base 10
var=v10; (( ${var#v} > 9 )) && echo YES # strip initial 'v'
If you want to prevent recursive variable expansion.
((...), curiously, expands a variable name without $ recursively, until its value is not the name of an existing variable anymore:
var1=10; var2=var1; (( var2 > 9 )) && echo YES
var2 expands to 10(!)
Has laxer whitespace rules.
Example: v1=0; ((v2 = 1 + ++v1)) && echo YES # -> $v1 == 1, $v2 == 2
Caveat: Since arithmetic evaluation behaves so differently from the rest of bash, you'll have to weigh its added features against having to remember an extra set of rules. You also pay a slight performance penalty1.
You can even cram arithmetic expressions, including assignments, into [[ conditionals that are based on numeric operators, though that may get even more confusing; e.g.:
v1=1 v2=3; [[ v1+=1 -eq --v2 ]] && echo TRUE # -> both $v1 and $v2 == 2
Note: In this context, by 'quoting' I mean single- or double-quoting an entire string, as opposed to \-escaping individual characters in a string not enclosed in either single- or double quotes.
1:
The following code - adapted from code by #konsolebox - was used for performance measurements:
Note:
The results can vary by platform - numbers are based on OS X 10.9.3 and Ubuntu 12.04.
[[ being nearly twice as fast as [ (factor around 1.9) is based on:
using unquoted, $-prefixed variable references in [[ (using double-quoted variable references slows things down somewhat)
(( is slower than [[ with unquoted, $-prefixed variable on both platforms: about 15-20% on OSX, around 30% on Ubuntu. On OSX, using double-quoted, $-prefixed variable references is actually slower, as is not using the $ prefix at all (works with numeric operators). By contrast, on Ubuntu, (( is slower than all ]] variants.
#!/usr/bin/env bash
headers=( 'test' '[' '[[/unquoted' '[[/quoted' '[[/arithmetic' '((' )
iterator=$(seq 100000)
{
time for i in $iterator; do test "$RANDOM" -eq "$RANDOM"; done
time for i in $iterator; do [ "$RANDOM" -eq "$RANDOM" ]; done
time for i in $iterator; do [[ $RANDOM -eq $RANDOM ]]; done
time for i in $iterator; do [[ "$RANDOM" -eq "$RANDOM" ]]; done
time for i in $iterator; do [[ RANDOM -eq RANDOM ]]; done
time for i in $iterator; do (( RANDOM == RANDOM )); done
} 2>&1 | fgrep 'real' | { i=0; while read -r line; do echo "${headers[i++]}: $line"; done; } | sort -bn -k3.3 | awk 'NR==1 { baseTime=substr($3,3) } { time=substr($3,3); printf "%s %s%%\n", $0, (time/baseTime)*100 }' | column -t
Outputs times from fastest to slowest, with slower times also expressed as a percentage of the fastest time.

Comparing variables in a Bash script

Looking at other Bash scripts, I see people comparing variables like: $S == $T while at other times I see the variable being wrapped inside strings: "$S" == "$T".
Some experiments seem to suggest that both do the same. The demo below will print equal in both cases (tested with GNU bash, version 4.2.37):
#!/usr/bin/env bash
S="text"
T="text"
if [[ $S == $T ]]; then
echo "equal"
fi
if [[ "$S" == "$T" ]]; then
echo "equal"
fi
My question: if there's a difference between $S == $T and "$S" == "$T", what is it?
If you use [[ they are almost the same, but not quite...
When the == and != operators are used, the string to the right of the operator is
considered a pattern and matched according to the rules described below under Pattern
Matching. [...]
Any part of the pattern may be quoted to force it to be matched as a string.
If you use [ then you have to use quotes unless you know that the variables cannot be empty or contain whitespace.
Just to be on the safe side, you probably want to quote all your variables all the time.

Resources