bash double bracket issue - linux

I'm very new to bash scripting and am running into an issue when using double brackets. I can't seem to get them to work at all in Ubuntu Server 11.10. My script below is in if_test.sh.
#!/bin/bash
if [[ "14"=="14" ]]; then
echo "FOO"
fi
When I run this simple shell script the output I get is: if_test.sh: 5: [[: not found
It seems that I'm running GNU bash version 4.2.10 after running bash --version from the terminal. Any help would be greatly appreciated. Thanks!

The problem lies in your script invocation. You're issuing:
$ sudo sh if_test.sh
On Ubuntu systems, /bin/sh is dash, not bash, and dash does not support the double bracket keyword (or didn't at the time of this posting, I haven't double-checked). You can solve your problem by explicitly invoking bash instead:
$ sudo bash if_test.sh
Alternatively, you can make your script executable and rely on the shebang line:
$ chmod +x if_test.sh
$ sudo ./if_test.sh
Also note that, when used between double square brackets, == is a pattern matching operator, not the equality operator. If you want to test for equality, you can either use -eq:
if [[ "14" -eq "14" ]]; then
echo "FOO"
fi
Or double parentheses:
if (( 14 == 14 )); then
echo "FOO"
fi

My answer doesn't apply to #lots_of_questions's question specifically, but you can also run into this problem if you have the wrong specifier at the top of your script:
#!/bin/sh
if [[ ... ]]
...
You should change that to
#!/bin/bash
if [[ ... ]]
...

Since you are new to scripting, you may be unaware that [[ is a bashism. You may not even know what a bashism is, but both answers given so far are leading you down a path towards a stunted scripting future by promoting their use.
To check if a variable matches a string in any flavor of Bourne shell, you can do test $V = 14 If you want to compare integers, use test $V -eq 14. The only difference is that the latter will generate an error if $V does not look like an integer. There are good reasons to quote the variable (test "$V" = 14), but the quotes are often unnecessary and I believe are the root cause of a common confusion, since "14"=="14" is identical to "14==14" where it is more obvious that '==' is not being used as an operator.
There are several things to note: use a single '=' instead of '==' because not all shells recognize '==', the [ command is identical to test but requires a final argument of ] and many sh coding guidelines recommend using test because it often generates more understandable code, [[ is only recognized by a limited number of shells (this is your primary problem, as your shell does not appear to recognize [[ and is looking for a command of that name. This is surprising if your shebang does indeed specify /bin/bash instead of /bin/sh).

Related

What would cause the BASH error "[: too many arguments" after taking measures for special characters in strings?

I'm writing a simple script to check some repositories updates and, if needed, I'm making new packages from these updates to install new versions of those programs it refers to (in Arch Linux). So I made some testing before executing the real script.
The problem is that I'm getting the error [: excessive number of arguments (but I think the proper translation would be [: too many arguments) from this piece of code:
# Won't work despite the double quoted $r
if [ "$r" == *"irt"* ]; then
echo "TEST"
fi
The code is fixed by adding double square brackets which I did thanks to this SO answer made by #user568458:
# Makes the code works
if [[ "$r" == *"irt"* ]]; then
echo "TEST"
fi
Note that $r is defined by:
# Double quotes should fix it, right? Those special characters/multi-lines
r="$(ls)"
Also note that everything is inside a loop and the loop progress with success. The problems occurs every time the if comparison matches, not printing the "TEST" issued, jumping straight to the next iteration of the loop (no problem: no code exists after this if).
My question is: why would the error happens every time the string matches? By my understanding, the double quotes would suffice to fix it. Also, If I count on double square brackets to fix it, some shells won't recognize it (refers to the answer mentioned above). What's the alternative?
Shell scripting seems a whole new programming paradigm.. I never quite grasp the details and fail to secure a great source for that.
The single bracket is a shell builtin, as opposed to the double bracket which is a shell keyword. The difference is that a builtin behaves like a command: word splitting, file pattern matching, etc. occur when the shell parses the command. If you have files that match the pattern *irt*, say file1irt.txt and file2irt.txt, then when the shell parses the command
[ "$r" = *irt* ]
it expands $r, matches all files matching the pattern *irt*, and eventually sees the command:
[ expansion_of_r = file1irt.txt file2irt.txt ]
which yields an error. No quotes can fix that. In fact, the single bracket form can't handle pattern matching at all.
On the other hand, the double brackets are not handled like commands; Bash will not perform any word splitting nor file pattern matching, so it really sees
[[ "expansion_of_r" = *irt* ]]
In this case, the right hand side is a pattern, so Bash tests whether the left hand side matches that pattern.
For a portable alternative, you can use:
case "$r" in
(*irt*) echo "TEST" ;;
esac
But now you have a horrible anti-pattern here. You're doing:
r=$(ls)
if [[ "$r" = *irt* ]]; then
echo "TEST"
fi
What I understand is that you want to know whether there are files matching the pattern *irt* in the current directory. A portable possibility is:
for f in *irt*; do
if [ -e "$f" ]; then
echo "TEST"
break
fi
done
Since you're checking for files with a certain file name, I'd suggest to use find explicitly. Something like
r="$(find . -name '*irt*' 2> /dev/null)"
if [ ! -z "$r" ]; then
echo "found: $r"
fi

Bash interprets && as an argument

Observe the following
[admin#myVM ~]$ test="echo hello && echo world"
[admin#myVM ~]$ $test
hello && echo world
[admin#myVM ~]$ echo hello && echo world
hello
world
It seems that if you place && into a bash variable and execute it as a command, bash interprets && as a string argument. Does anyone know how to alter this behavior? That is to say, does anyone know how to tell bash “I want to execute double ampersand”.
Variables are not meant to store scripting. Store code in functions, not variables.
$ test() { echo hello && echo world; }
$ test
hello
world
The reason $test doesn't work is because unquoted variable expansions are only subjected to two of bash's many phases of parsing:
Word splitting
Globbing
That means it will split apart $test into separate words, which is why it recognizes the echo command. It will also expand wildcards AKA globs. Those are the only two bits of parsing it does, though. It doesn't look for symbols like &&, |, or ;.
I don't recommend it, but you can get bash to execute your string with bash -c "$test" or eval "$test". These are bad habits that you should avoid unless absolutely necessary. Functions are much better than hacks with eval, and they don't open up potential security holes by allowing arbitrary code execution.

comparison [ "$var" == "value" ] always false; how can this be debugged?

I'm having a ton of trouble with an if statement in bash. Here's what I have:
if [ "$returncode" == "HTTP/1.1 200 OK" ]; then
echo "Works!"
fi
This will not work successfully. I've verified over and over again that the variable $returncode is equal to the text specified, I'll print the two values right before this and they are definitely identical.
I've tried a couple different variations of this statement, all with no luck. I feel like a moron because this should be extremely simple! What do you think is wrong here?
If the values truly are identical (no hidden characters), the most likely problem is that your shell isn't actually bash.
/bin/sh only promises compliance with the POSIX sh standard, and == isn't a valid comparison operator in POSIX test; the standard-compliant string comparison operator is =.
Also try eliminating a CR from the end of the line:
#!/bin/bash
# ^^^^ NOT /bin/sh ($'...' is a non-POSIX extension)
# ...and if starting with an explicit interpreter, "bash yourscript", not "sh yourscript"
set -x # this will log each command run to stderr, allowing easy debugging
if [ "${returncode%$'\r'}" = "HTTP/1.1 200 OK" ]; then
echo "Works!"
fi
${foo%val} expands to the contents of $foo with any suffix consisting of val removed; $'\r' is, in bash, a constant referring to the ASCII CR character, which would be present if reading a CRLF-terminated string using standard UNIX tools (which expect LF-only newlines).

bash script: if use with (conditon) or [condition]

I have a subtle question about if statement in bash script: what type of parenthesis should follow "if"?
I have two statements both working right now, but not working if I exchange () and [].
1.compare variable name
NAME="dada"
if [ "$NAME" == "dada" ]; then SOME COMMANDS; fi
If change [] to (), error:
dada: command not found
2.using grep
if ( grep -q "lalala" mytest.txt ); then SOME COMMANDS; fi
If change () to [], error:
[: lalala: binary operator expected
I read the linux man, which suggest using []. So is it because of using grep? Can we say generally if using another program inside if, we should use ()?
A side question about double quotation marks,
is it necessary to quote text in shell? for example, echo "$NAME" or NAME="fadfaj"?
I got confused because when I try to use $NAME plus some special characters such as "_" or space, where I need to use "\", I got different behaviour with and without "".
NAME="myfolder"
echo $NAME\_na
echo "$NAME\_na"
output
myfolder_na
myfolder\_na
I understand these are naive questions but I do appreciate all your help!
The thing following the if keyword is a command. The command is executed, and the condition is true if the command succeeds (exits with a status of 0), false if it fails.
Though it looks like shell syntax, the [ symbol is actually the name of a command (which happens to be built into the shell). For example, you could type just
[ "$NAME" = "dada" ]
at a shell prompt, with no if, and it would evaluate the same condition and set $?. You can also do this:
[ "$NAME" = "dada" ] && echo Yes
The ] is a required last argument to the [ command. Apart from that requirement, the [ command is equivalent to the test command:
if test "$Name" = "data" ; then echo yes ; fi
Note that I've used = rather than ==. The built-in test/[ command in bash recognizes either form, but = is the original syntax, and some implementations don't recognize ==.
[ is the most common command to use with if, but you can use any command, including `grep:
if grep -q "lalala" mytest.txt; then SOME COMMANDS; fi
The parentheses are unnecessary. (If you add them, you're using a compound command rather than a simple command; there's no need to do so.)
See the bash manual (info bash) for more information. Search for
`test'
(with the backtick and apostrophe) for information on the built-in test command.
Bash also has a [[ command which has some advantages over the older [.

shell string bad substitution

I'm new to shell programming. I intend to get directory name after zip file was extracted. The print statement of it is
$test.sh helloworld.zip
helloworld
Let's take a look at test.sh:
#! /bin/sh
length=echo `expr index "$1" .zip`
a=$1
echo $(a:0:length}
However I got the Bad substitution error from the compiler.
And when I mention about 'shell'.I just talking about shell for I don't know the difference between bash or the others.I just using Ubuntu 10.04 and using the terminal. (I am using bash.)
If your shell is a sufficiently recent version of bash, that parameter expansion notation should work.
In many other shells, it will not work, and a bad substitution error is the way the shell says 'You asked for a parameter substitution but it does not make sense to me'.
Also, given the script:
#! /bin/sh
length=echo `expr index "$1" .zip`
a=$1
echo $(a:0:length}
The second line exports variable length with value echo for the command that is generated by running expr index "$1" .zip. It does not assign to length. That should be just:
length=$(expr index "${1:?}" .zip)
where the ${1:?} notation generates an error if $1 is not set (if the script is invoked with no arguments).
The last line should be:
echo ${a:0:$length}
Note that if $1 holds filename.zip, the output of expr index $1 .zip is 2, because the letter i appears at index 2 in filename.zip. If the intention is to get the base name of the file without the .zip extension, then the classic way to do it is:
base=$(basename $1 .zip)
and the more modern way is:
base=${1%.zip}
There is a difference; if the name is /path/to/filename.zip, the classic output is filename and the modern one is /path/to/filename. You can get the classic output with:
base=${1%.zip}
base=${base##*/}
Or, in the classic version, you can get the path with:
base=$(dirname $1)/$(basename $1 .zip)`.)
If the file names can contain spaces, you need to think about using double quotes, especially in the invocations of basename and dirname.
Try running it with bash.
bash test.sh helloworld.zip
-likewise-
"try changing the first line to #!/bin/bash" as comment-answered by – #shellter
Try that in bash :
echo $1
len=$(wc -c <<< "$1")
a="${1}.zip"
echo ${a:0:$len}
Adapt it to fit your needs.

Resources