Reason of adding "_" in the if comparison "if [ "_$str" = "_" ]; then ....; fi" - linux

I see many times in the shell scripts that they use "_" in the if comparison as indicated below:
if [ "_$str" = "_" ]; then ....; fi
the above code check if the str variable is empty with the comparison if [ "_$str" = "_" ].
Why do not use if [ "$str" = "" ]? why adding "_" in both strings?

As far as I know it is for historical reasons. There once were (and maybe on some obscure systems still are) shells which had (have) trouble handling empty strings (and maybe strings starting with dashes). Hence the simple idea to prevent empty strings (and option-like looking ones) altogether by adding a prefix. All common shells today do not have these problems anymore.

I can only guess that before learning how to use quotes properly, the writers of the scripts you've seen had trouble with something like
if [[ $str = "" ]]; then ....
which will fail if $str is empty, as the argument expands to nothing. Whereas
if [[ _$str = _ ]]
will work, but is a kludge.

Remember that [ is an alias to the command test. The question is what would happen if your string started with a dash?
In old versions of test when using the standard Bourne shell, you'd get an error:
$ test -gt = "some_string"
invalid argument
The same thing would happen with an if
$ string="-gt"
$ if [ "$string" = "some_other_string" ]
> then
> echo "Match"
> else
> echo "No Match"
> fi
if: invalid argument
That's because the test command would see the parameter with a dash and assume that it's a command argument, then either it's an invalid command argument, or the format of the command is incorrect. If you use [[ instead of [, it's not an issue at all because [[ is a built in test for the if.
This is no longer an issue. The [ and test are internal commands to both the Kornshell and BASH, and these shells can handle this issue. Even the newer versions of test are no longer thrown:
$ test -gt = "some_string" # No error.
$ echo $?
1
However, older scripts, and those people who either learned from an older timer, or were back writing scripts when system with 32 Mb of memory running on a 16 Mhz 386 chip with Xenix was a state of the art system have gotten into the habit. I use to use x:
if [ x$var = x$foo ]

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

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 [.

Bash if else string variable comparison

I am writing a shell script at which I am trying to compare 2 variables that are strings. Every thing works fine as in all the variables have a value from the commands however my if then else statement is not working.
#!/bin/bash
name=$1
for i in {1...10}
do
username=sudo cat /class/rolls/CSCE215-*|awk 'BEGIN {FIELDWIDTHS = "32 20"} {print $2}'|cut -d " " -f6 |sed -n '1,1p'
if ["$name" == "$username"]
then
echo Correct Username
else
echo Incorrect Username
fi
done
All of the tutorials and help online appears to have what I have here but I cannot find out what is wrong with mine.
You are using the "classic test" but it's a good idea to ALWAYS use the newer conditional expression: http://wiki.bash-hackers.org/syntax/ccmd/conditional_expression
if [[ "$name" == "$username" ]]
As far as I know the test command (with a single bracket) doesn't even officially support the "==" operator..
Oh, and of course don't forget the spaces inside the brackets, bash needs them to break the line up into words.
When using test or [, the correct comparison is:
test "$string1" = "string2"
or
[ "$sting1" = "$string2" ]
Note: the single = instead of ==, and always quote sting variables. Further, there is nothing wrong with using the test or [ operators, in fact, they are preferred when portability is needed. They simply lack some of the extended functionality of the [[ operator, such as character class comparison and the ability to use =~.
Now when using the [[ operator, the correct form is:
[[ "$sting1" == "$string2" ]]
Note: as pointed out, quotes are not required when using the [[ operator, but if you get in the habit of always quoting strings, you will be safe in both cases.

bash double bracket issue

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).

Resources