Double-bracket if-statement not working - bash

if [[ $nb_dd > 0 ]]; then
INPUT_TYPE="txt"
fi
if [ $nb_dd > 0 ]; then
INPUT_TYPE="txt"
fi
The first check (using double brackets) fails to execute, causing the script to exit upon error as $INPUT_TYPE is not populated - but the latter version (single bracket) works, correctly setting $INPUT_TYPE. Why is this?
I'm running on OS X (10.11.4) (echo $SHELL = /bin/bash) - this is from a widely distributed suite of tools which were presumingly built on a Linux platform - and the .sh script otherwise works for the author so it may be a platform-specific issue although I can't think why.

They both "work" for me, however you are using the wrong type of test. The > inside [[ is doing a textual comparison. For an arithmetic comparison either use the old -gt or (better) the correct brackets - double parentheses:
if (( nb_dd > 0 )); then
INPUT_TYPE="txt"
fi
Note that the $ is not used, since only numerics can be compared inside ((...)) (using a $ inside might work but can give strange side-effects because of expansion order).

You must gt and lt to make comparisons,
if [[ $nb_dd -gt 0 ]];
then INPUT_TYPE="txt";
fi
if [ $nb_dd -gt 0 ];
then INPUT_TYPE="txt";
fi
Double brackets just extend posix functionality
> means nothing in posix compliant test expressions, and it just calls the output redirection.
[ 2 > 3 ] && echo "shouldn't print this"
[ 2 -gt 3 ] && echo "this isn't printed"
On extensions expressions to the posix ones it means sting comparison.
[[ "aa" > "ab" ]] && echo "doesn't print"
[[ "aa" > "aa" ]] && echo "doesn't print"
[[ "ab" > "aa" ]] && echo "prints"

Related

Stumped: this script does not work as expected?

I'm stumped when writing a simple script.
Essentially the $u variable is does not take u=$USER. Here's the code:
#!/bin/bash
if [ $# > 0 ] ; then
u=$1
else
u=$USER
fi
echo $u
The fix
You have 2 equally viable options:
Use -gt
if [ $# -gt 0 ]
Use double brackets [[ (Does a lexicographic comparison but will work for this case)
if [[ $# > 0 ]]
Why?
When you did if [ $# > 0 ] the > was treated like an output redirection command similar to echo "foo" > file.txt. You might notice you have created a file named 0 someplace after executing:
if [ $# > 0 ]
When deciding between using [...] or [[...]] many find the answer is to use double brackets
Getting fancy
Now if what you'd really like to do is write a script that gives a default value to the u variable if none is provided by the first argument I would recommend using a neat bash syntax trick for implementing default values
u=${1:-${USER}}
I believe I found the answer using double brackets
#!/bin/bash
if [[ $# > 0 ]] ; then
u=$1
else
u=${USER}
fi
echo $u
Not sure I fully understand why it failed with single brackets.

Can I omit the then part in an if statement?

How would be the correct bash syntax for something like this:
if [ "$actual" == "$expected" ]; then
doNothing
else
echo "Error: actual: $actual. Expected: $expected"
fi
I am looking for something that works for all possible values of the variables "actual" and "expected". The content of the variables must not be interpreted/evaluated/expanded in any way. The script does not need to be portable (a bash only solution is ok).
You could use the simplest do-nothing statement available:
if [ "$actual" = "$expected" ]; then
:
else
echo "Error: actual: $actual. Expected: $expected"
fi
(Note: One = not two in [/test.)
But a better idea is to just invert the test and remove the need for that entirely:
if [ "$actual" != "$expected" ]; then
echo "Error: actual: $actual. Expected: $expected"
fi
Did you try:
if [ "$actual" != "$expected" ]; then
echo "Error: actual: $actual. Expected: $expected"
fi
if [[ $actual != $expected ]]
then
echo "Error: actual: $actual. Expected: $expected"
fi
Using the builtin [[ has several advantages over test / [. For one, you don't get bitten if you don't quote variables containing whitespace.
[[ ]] also offers < and > for locale-aware lexicographic sorting, regular expression matching, and =~. Check man bash.
(Note Etan's comment though on at least one dissenting opinion. I haven't yet made up my mind whether this is a disadvantage or a feature to be exploited, but it is sure surprising.)
There's also the thing with putting then on a separate line, but that's just personal preference.

octal expansion difference between [ and [[?

As far as I had known, [[ and [ can be expected to behave mostly the same, taking into account a few extra features [[ has. But recently I noticed a discrepancy in how bash treats octal expansions:
$ b=010; echo $((b))
8
$ [[ $b -eq 8 ]]; echo $?
0
but
$ [ $b -eq 8 ]; echo $?
1
$ test $b -eq 8; echo $?
1
$ [ $b -eq 10 ]; echo $?
0
Why does the latter expression drop the auto octal conversion? Expressions like -eq are "Arithmetic" according to help test in Bash and the Bash Reference Manual, and further according to the next section of the reference manual constants with a leading zero can be treated as octal.
POSIX sh is a little less clear on the subject: Even though POSIX arithmetic expressions still expand leading-zero integers to their octal value, it refers to -eq expressions in test as algebraic, not arithmetic.
Is there any documentation or evidence to suggest that bash makes a distinction between [[ and [ for octal expansion on purpose, or is it just an accidental feature?
[[ is known as the extended test command and behaves as ksh88, you can find an elaboration here:
http://tldp.org/LDP/abs/html/testconstructs.html#DBLBRACKETS
Also if you need to be sure of the base in bash you can use the # base operator like this:
b=010; echo $((10#$b))
10
b=10; echo $((8#$b)
8
b=013; echo $((8#$b))
11

Removing files in Unix using bash

I'm trying to delete a large amount of files from my computer, and I'm trying to write a bash script to do so using the rm command. What I want to know is how to do equality in bash, and why my code (posted below) won't compile. Thank you for your help!
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if ["$i" -eq "$e1"]
then
b="000"
echo $b$
fi
if ["$i" -eq "$e2"]
then
b='00'
fi
if ["$i" -eq "$e3"]
then
b='0'
fi
if ["$i" -eq "$e4"]
then
b =''
fi
if [bash$ expr "$i" % "$e3$ -ne 0]
then
d = $b$c$a
rm d
fi
done
Shell scripts aren't compiled at all.
You need spaces after your [ and before your ].
if [ "$i" -eq "$e1" ]
There's an errant bash$ in there you probably don't want at all. It should probably be a $() operator:
if [ $(expr "$i" % "$e3") -ne 0 ]
You can't have spaces around the = in bash. For example, change b ='' to b='' and d = $b$c$a to d=$b$c$a.
echo $b$ looks like it should be echo $b.
Shell script does not compile it is a scripting language.
Try to fix this line :
if [bash$ expr "$i" % "$e3$ -ne 0]
Make it like below :
if [ $(expr "$i" % "$e3$") -ne 0 ]
You need spaces around the square brackets. The [ is actually a command, and like all commands needs to be delineated by white space.
When you set values for variables in shell, you do not put spaces around the equals signs.
Use quotation marks when doing comparisons and setting values to help delineate your values.
What happens if none of the if conditions are true, and $b isn't set.
What is the logic behind this code. It seems to be a bunch of random stuff. You're incrementing $ from 1 to 10000, but only setting the value of $b on only four of those values. Every 200 steps, you delete a file, but $b may or may not be set even though it's part of the file name.
Did you write this program yourself? Did you try to run it? What errors were you getting? Did you look at the lines referenced by those errors. It looks like you included the bash$ prompt as part of the command.
There were plenty of errors, and I've cleaned most of them up. The cleaned up code is posted below, but it still doesn't mean it will do what you want. All you said is you want to delete "a large amount of files" on your computer, but gave no other criteria. You also said "What I want to know is how to do equality in bash" which is not the question you stated in you header.
Here's the code. Note the changes, and it might lead to whatever answer you were looking for.
#!/bin/bash
# int-or-string.sh
b="0000"
c="linorm"
f=500
e1=2
e2=20
e3=200
e4=2000
for i in {0..10000}
do
a=$(($f*$i))
if [ "$i" -eq "$e1" ]
then
b="000"
elif [ "$i" -eq "$e2" ]
then
b='00'
elif [ "$i" -eq "$e3" ]
then
b='0'
elif [ "$i" -eq "$e4" ]
then
b=''
fi
if ! $(($i % $e3))
then
d="$b$c$a"
rm "$d"
fi
done
ERRORS:
Spaces around the [ and ]
The rm "$d" command was originallyrm dwhich would just remove a file namedd`.
if/then statement converted to if/else if.
Rewrote [ $(expr "$1" % "$e3") -ne 0 ].
No need for expr since BASH has $((..)) syntax.
No need for test command ([) since if automatically evaluates zero to true and non-zero to false.
Added quotes.

Meaning of "[: too many arguments" error from if [] (square brackets)

I couldn't find any one simple straightforward resource spelling out the meaning of and fix for the following BASH shell error, so I'm posting what I found after researching it.
The error:
-bash: [: too many arguments
Google-friendly version: bash open square bracket colon too many arguments.
Context: an if condition in single square brackets with a simple comparison operator like equals, greater than etc, for example:
VARIABLE=$(/some/command);
if [ $VARIABLE == 0 ]; then
# some action
fi
If your $VARIABLE is a string containing spaces or other special characters, and single square brackets are used (which is a shortcut for the test command), then the string may be split out into multiple words. Each of these is treated as a separate argument.
So that one variable is split out into many arguments:
VARIABLE=$(/some/command);
# returns "hello world"
if [ $VARIABLE == 0 ]; then
# fails as if you wrote:
# if [ hello world == 0 ]
fi
The same will be true for any function call that puts down a string containing spaces or other special characters.
Easy fix
Wrap the variable output in double quotes, forcing it to stay as one string (therefore one argument). For example,
VARIABLE=$(/some/command);
if [ "$VARIABLE" == 0 ]; then
# some action
fi
Simple as that. But skip to "Also beware..." below if you also can't guarantee your variable won't be an empty string, or a string that contains nothing but whitespace.
Or, an alternate fix is to use double square brackets (which is a shortcut for the new test command).
This exists only in bash (and apparently korn and zsh) however, and so may not be compatible with default shells called by /bin/sh etc.
This means on some systems, it might work from the console but not when called elsewhere, like from cron, depending on how everything is configured.
It would look like this:
VARIABLE=$(/some/command);
if [[ $VARIABLE == 0 ]]; then
# some action
fi
If your command contains double square brackets like this and you get errors in logs but it works from the console, try swapping out the [[ for an alternative suggested here, or, ensure that whatever runs your script uses a shell that supports [[ aka new test.
Also beware of the [: unary operator expected error
If you're seeing the "too many arguments" error, chances are you're getting a string from a function with unpredictable output. If it's also possible to get an empty string (or all whitespace string), this would be treated as zero arguments even with the above "quick fix", and would fail with [: unary operator expected
It's the same 'gotcha' if you're used to other languages - you don't expect the contents of a variable to be effectively printed into the code like this before it is evaluated.
Here's an example that prevents both the [: too many arguments and the [: unary operator expected errors: replacing the output with a default value if it is empty (in this example, 0), with double quotes wrapped around the whole thing:
VARIABLE=$(/some/command);
if [ "${VARIABLE:-0}" == 0 ]; then
# some action
fi
(here, the action will happen if $VARIABLE is 0, or empty. Naturally, you should change the 0 (the default value) to a different default value if different behaviour is wanted)
Final note: Since [ is a shortcut for test, all the above is also true for the error test: too many arguments (and also test: unary operator expected)
Just bumped into this post, by getting the same error, trying to test if two variables are both empty (or non-empty). That turns out to be a compound comparison - 7.3. Other Comparison Operators - Advanced Bash-Scripting Guide; and I thought I should note the following:
I used -e thinking it means "empty" at first; but that means "file exists" - use -z for testing empty variable (string)
String variables need to be quoted
For compound logical AND comparison, either:
use two tests and && them: [ ... ] && [ ... ]
or use the -a operator in a single test: [ ... -a ... ]
Here is a working command (searching through all txt files in a directory, and dumping those that grep finds contain both of two words):
find /usr/share/doc -name '*.txt' | while read file; do \
a1=$(grep -H "description" $file); \
a2=$(grep -H "changes" $file); \
[ ! -z "$a1" -a ! -z "$a2" ] && echo -e "$a1 \n $a2" ; \
done
Edit 12 Aug 2013: related problem note:
Note that when checking string equality with classic test (single square bracket [), you MUST have a space between the "is equal" operator, which in this case is a single "equals" = sign (although two equals' signs == seem to be accepted as equality operator too). Thus, this fails (silently):
$ if [ "1"=="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] ; then echo A; else echo B; fi
A
$ if [ "1"="" ] && [ "1"="1" ] ; then echo A; else echo B; fi
A
$ if [ "1"=="" ] && [ "1"=="1" ] ; then echo A; else echo B; fi
A
... but add the space - and all looks good:
$ if [ "1" = "" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" ] ; then echo A; else echo B; fi
B
$ if [ "1" = "" -a "1" = "1" ] ; then echo A; else echo B; fi
B
$ if [ "1" == "" -a "1" == "1" ] ; then echo A; else echo B; fi
B
Another scenario that you can get the [: too many arguments or [: a: binary operator expected errors is if you try to test for all arguments "$#"
if [ -z "$#" ]
then
echo "Argument required."
fi
It works correctly if you call foo.sh or foo.sh arg1. But if you pass multiple args like foo.sh arg1 arg2, you will get errors. This is because it's being expanded to [ -z arg1 arg2 ], which is not a valid syntax.
The correct way to check for existence of arguments is [ "$#" -eq 0 ]. ($# is the number of arguments).
I also faced same problem. #sdaau answer helped me in logical way. Here what I was doing which seems syntactically correct to me but getting too many arguments error.
Wrong Syntax:
if [ $Name != '' ] && [ $age != '' ] && [ $sex != '' ] && [ $birthyear != '' ] && [ $gender != '' ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "Enter all the values"
fi
in above if statement, if I pass the values of variable as mentioned below then also I was getting syntax error
export "Name"="John"
export "age"="31"
export "birthyear"="1990"
export "gender"="M"
With below syntax I am getting expected output.
Correct syntax:
if [ "$Name" != "" -a "$age" != "" -a "$sex" != "" -a "$birthyear" != "" -a "$gender" != "" ]
then
echo "$Name"
echo "$age"
echo "$sex"
echo "$birthyear"
echo "$gender"
else
echo "it failed"
fi
There are few points which we need to keep in mind
use "" instead of ''
use -a instead of &&
put space before and after operator sign like [ a = b], don't use as [ a=b ] in if condition
Hence above solution worked for me !!!
Some times If you touch the keyboard accidentally and removed a space.
if [ "$myvar" = "something"]; then
do something
fi
Will trigger this error message. Note the space before ']' is required.
I have had same problem with my scripts. But when I did some modifications it worked for me. I did like this :-
export k=$(date "+%k");
if [ $k -ge 16 ]
then exit 0;
else
echo "good job for nothing";
fi;
that way I resolved my problem. Hope that will help for you too.

Resources