Different behavior on escaping double quotes - shell

I have a empty variable. When I do
if [[ -z "$value" ]]; then echo "hello"; fi
the output is hello
However, when I escape double quotes, i.e.
if [[ -z \"$value\" ]]; then echo "hello"; fi
hello is not printed on the screen.
What is the difference between above two commands?

It is because the in the first statement, the variable is expanded and the resulting statement is null.
Whereas in the second it expands to "". To see the difference consider the two echo statements,
$ echo "$value"
$ echo \"$value\"
""
The first one is null where as the second is not.

There are three kind of quotes in Shell.
Single quote(' ')
All special characters between these quotes lose their special meaning.
so echo '$VALUE'
will literally print $VALUEbecause $loses its special meaning
inside single quotes.
double Quotes(" ")
Most special characters between these quotes lose their special meaning with some exceptions like $ among others.
Back Quote ()
Anything in between back quotes would be treated as a command and would be executed.
Backslash
Any character immediately following the backslash loses its special meaning.
so in your case when you say \"$value\" because of backslash double quotes will loose meaning and will be evaluated as it is.
e.g if you have value=34 then \"$value\" will evaluate to "34" with the double quotes. and if value is null as in your case then \"$value\" will evaluate to ""
so it is not null but a string with double quotes and that's why fail your null check.

if [[ -z "$value" ]]; then echo "hello"; fi checks if the string value in the variable $value is non-empty. But if [[ -z \"$value\" ]]; then echo "hello"; fi (assuming $value does not contain whitespace) checks if the string that starts with a " and ends with a " is non-empty. But it never is, because it always contains at least two characters.

Related

Bash script creates a file which it shouldn't be

This following script will creates a file named "Happy", i couldn't figure out why, can someone try this script and tell me what is happening?
Thanks!
#!/bin/bash
str1=""
str2="Sad"
str3="Happy"
if [ "$str2">"$str3" ]
then echo "$str2 is greater than $str3"
elif [ "$str2"<"$str3" ]
then echo "$str2 is less than $str3"
fi
[ is just a (silly) alias for the test command; everything following it (including the mandatory closing ]) is an argument. (What you have is treated the same as if test "$str2">"$str3".)
There are two issues:
The operands and the operators need to be separated by whitespace.
# still not quite right, but ...
if [ "$str2" > "$str3" ]
Since > and < are interpreted as redirection operators by the shell, they have to be escaped so that they are passed as arguments to [.
if [ "$str2 \> "$str3" ]
(You might think just escaping the operator would be sufficient; however, "$str2"\>"$str3" would be treated as a single string argument to test rather than three separate arguments which test will interpret as an expression. test "$str2"\>"$str3" would simply check if the single argument is empty or not.)
Since you are using bash, it's far simpler to just use [[ instead of [. [[ is not a regular command; it's special syntax recognized by bash, so that normal processing rules don't apply. Most relevant here is that > is a string comparison operator, not a redirection operator, so it doesn't need to be escaped.
if [[ $str2 > $str3 ]]
If you rewrite the code as follows:
#!/bin/bash
str1=""
str2="Sad"
str3="Happy"
if [[ "$str2" > "$str3" ]]
then echo "$str2 is greater than $str3"
elif [[ "$str2" < "$str3" ]]
then echo "$str2 is less than $str3"
fi
then the comparisons should occur correctly and you can avoid inadvertent file creation. The extra pair of "[" and "]" takes the code out of test context, allowing for comparison and avoids file creation. More info here which states the following:
Note that the ">" needs to be escaped within a [ ] construct.
...
Note that the "<" needs to be escaped within a [ ] construct.
The reason is that in a test context, i.e. using only a single pair of square brackets as in the OP code, ">" and "<" are interpreted as redirection operators. So, instead of meaning greater than and less than respectively, ">" means direct the output of a command to a file whereas "<" means give input to a command.

Shell script Bash, Check if string starts and ends with single quotes

I need to check if a string starts and ends with a single quote, for example
'My name is Mozart'
What I have is this, which doesn't work
if [[ $TEXT == '*' ]] ;
This does not work either
if [[ $TEXT == /'*/' ]] ;
But if I change it to
if [[ $TEXT == a*a ]] ;
it works for a sentence like 'an amazing apa'. So I Believe it has to do with the single quote sign.
Any ideas on how I can solve it?
With a regex:
if [[ $TEXT =~ ^\'.*\'$ ]]
With globbing:
if [[ $TEXT == \'*\' ]]
I am writing the complete bash script so you won't have any confusion:
#! /bin/bash
text1="'helo there"
if [[ $text1 =~ ^\'.*\'$ ]]; then
echo "text1 match"
else
echo "text1 not match"
fi
text2="'hello babe'"
if [[ $text2 =~ ^\'.*\'$ ]]; then
echo "text2 match"
else
echo "text2 not match"
fi
Save the above script as
matchCode.sh
Now run it as:
./matchCode
output:
text1 not match
text2 match
Ask if you have any confusion.
Cyrus' helpful answer solves your problem as posted.
However, I suspect you may be confused over quotes that are part of the shell syntax vs. quotes that are actually part of the string:
In a POSIX-like shell such as Bash, 'My name is Mozart' is a single-quoted string whose content is the literal My name is Mozart - without the enclosing '. That is, the enclosing ' characters are a syntactic elements that tell the shell that everything between them is the literal contents of the string.
By contrast, to create a string whose content is actually enclosed in ' - i.e., has embedded ' instances, you'd have to use something like: "'My name is Mozart'". Now it is the enclosing " instances that are the syntactic elements that bookend the string content.
Note, however, that using a "..." string (double quotes) makes the contents subject to string interpolation (expansion of embedded variable references, arithmetic and command substitutions; none in the case at hand, however), so it's important to know when to use '...' (literal strings) vs. "..." (interpolated strings).
Embedding ' instances in '...' strings is actually not supported at all in POSIX-like shells, but in Bash, Ksh, and Zsh there's another string type that allows you to do that: ANSI C-quoted strings, $'...', in which you can embed ' escaped as \': $'\'My name is Mozart\''
Another option is to use string concatenation: In POSIX-like shells, you can place substrings employing different quoting styles (including unquoted tokens) directly next to one another in order to form a single string: "'"'My Name is Mozart'"'" would also give you a string with contents 'My Name is Mozart'.
POSIX-like shells also allow you to escape individual, unquoted characters (meaning: neither part of a single- nor a double-quoted string) with \; therefore, \''My name is Mozart'\' yields the same result.
The behavior of Bash's == operator inside [[ ... ]] (conditionals) may have added to the confusion:
If the RHS (right-hand side - the operand to the right of operator ==) is quoted, Bash treats it like a literal; only unquoted strings (or variable references) are treated as (glob-like) patterns:
'*' matches literal *, whereas * (unquoted!) matches any sequence of characters, including none.
Thus:
[[ $TEXT == '*' ]] would only ever match the single, literal character *.
[[ $TEXT == /'*/' ]], because it mistakes / for the escape character - which in reality is \ - would only match literal /*/ (/'*/' is effectively a concatenation of unquoted / and single-quoted literal */).
[[ $TEXT == a*a ]], due to using an unquoted RHS, is the only variant that actually performs pattern matching: any string that starts with a and ends with a is matched, including aa (because unquoted * represents any sequence of characters).
To verify that Cyrus' commands do work with strings whose content is enclosed in (embedded) single quotes, try these commands, which - on Bash, Ksh, and Zsh - should both output yes.
[[ "'ab'" == \'*\' ]] && echo yes # pattern matching, indiv. escaped ' chars.
[[ "'ab'" =~ ^\'.*\'$ ]] && echo yes # regex operator =~

Is unset parameter in [[ compound command expected as empty string in bash?

I am learning [[ compound command, that is, new if sentence.
Some sites including bashFAQ/31 on What is the difference between test, [ and [[ ?, say that it does not need to quote parameter in it.
My question is what exactly happened with unquoted unset parameter.
I tried following code,
unset UNDEF_VAR
[[ ${UNDEF_VAR} = "" ]]
echo "result is $?"
and the output was
result is 0
So, I expect that unset variable, ${UNDEF_VAR}, between [[ ]] is translated as empty string on the contrary to the case of old if, "[".
But unfortunately, I could not find the explicit explanation in sites above and bash manual. Some sites just says it is OK without the reason.
Someone who knows the mechanism, please let me know. If I missed the explanation in sites/manual carelessly, please forgive me for using your time. Thank you very much.
As far as I can find, the only reference to the expansion of an unset parameter is this sentence from the POSIX spec:
The value, if any, of parameter shall be substituted.
A consequence of this is that "$foo" is an empty string because no value is placed between the quotes.
The bash man page has this to say about word-splitting:
Explicit null arguments ("" or '') are retained. Unquoted implicit
null arguments, resulting from the expansion of parameters that have no
values, are removed. If a parameter with no value is expanded within
double quotes, a null argument results and is retained.
Inside [[ ... ]], a parameter expansion does not undergo word-splitting, so it is not clear exactly where "no value to substitute" becomes "null value". I assume that parameter expansions are treated as being implicitly quoted, whether or not they actually are.
The [[ version behaves much more like you would expect it (at least as I would). It's independent if the variable is unset or empty.
$ unset foo
$ [[ $foo == "" ]] && echo true || echo false
true
$ foo=""
$ [[ $foo == "" ]] && echo true || echo false
true
With [ you have to be careful. You need to quote it, if there's any chance that it might be empty or unset:
$ [ $foo = "" ] && echo true || echo false
bash: [: =: unary operator expected
false
$ [ "$foo" = "" ] && echo true || echo false
true
Also note that when you use [[, you should compare with ==, and if you use [, compare with =. AFAIR zsh causes trouble, if you mix them.
And of course, as #fedorqui noted, comparing empty strings should be done with -z.

Search in string for multiple array values

I'm looking at a simple for loop with the following logic:
variable=`some piped string`
array_value=(1.1 2.9)
for i in ${array_value[#]}; do
if [[ "$variable" == *some_text*"$array_value" ]]; then
echo -e "Info: Found a matching string"
fi
The problem is that I cannot get this to show me when it finds either the string ending in 1.1 or 2.9 as sample data.
If I do an echo $array_value in the for loop I can see that the array values are being taken so its values are being parsed, though the if loop doesn't return that echo message although the string is present.
LE:
Based on the comments received I've abstracted the code to something like this, which still doesn't work if I want to use wildcards inside the comparison quote
versions=(1.1 2.9)
string="system is running version:2.9"
for i in ${versions[#]}; do
if [[ "$string" == "system*${i}" ]]; then
echo "match found"
fi
done
Any construction similar to "system* ${i}" or "* ${i}" will not work, though if I specify the full string pattern it will work.
The problem with the test construct has to you with your if statement. To construct the if statement in a form that will evaluate, use:
if [[ "$variable" == "*some_text*${i}" ]]; then
Note: *some_text* will need to be replaced with actual text without * wildcards. If the * is needed in the text, then you will need to turn globbing off to prevent expansion by the shell. If expansion is your goal, then protect the variable i by braces.
There is nothing wrong with putting *some_text* up against the variable i, but it is cleaner, depending on the length of some_text, to assign it to a variable itself. The easiest way to accommodate this would be to define a variable to hold the some_text you are needing. E.g.:
prefix="some_text"
if [[ "$variable" == "${prefix}${i}" ]]; then
If you have additional questions, just ask.
Change "system*${i}" to system*$i.
Wrapping with quotes inside [[ ... ]] nullifies the wildcard * by treating it as a literal character.
Or if you want the match to be assigned to a variable:
match="system*"
you can then do:
[[ $string == $match$i ]]
You actually don't need quotes around $string either as word splitting is not performed inside [[ ... ]].
From man bash:
[[ expression ]]
...
Word splitting and pathname expansion are not
performed on the words between the [[ and ]]
...
Any part of the pattern may be quoted to force
the quoted portion to be matched as a string.

Comparison of 2 string variables in shell script

Consider there is a variable line and variable word:
line = 1234 abc xyz 5678
word = 1234
The value of these variables are read from 2 different files.
I want to print the line if it contains the word. How do I do this using shell script? I tried all the suggested solutions given in previous questions. For example, the following code always passed even if the word was not in the line.
if [ "$line"==*"$word"*]; then
echo $line
fi
No need for an if statement; just use grep:
echo $line | grep "\b$word\b"
You can use if [[ "$line" == *"$word"* ]]
Also you need to use the following to assign variables
line="1234 abc xyz 5678"
word="1234"
Working example -- http://ideone.com/drLidd
Watch the white spaces!
When you set a variable to a value, don't put white spaces around the equal sign. Also use quotes when your value has spaced in it:
line="1234 abc xyz 5678" # Must have quotation marks
word=1234 # Quotation marks are optional
When you use comparisons, you must leave white space around the brackets and the comparison sign:
if [[ $line == *$word* ]]; then
echo $line
fi
Note that double square brackets. If you are doing pattern matching, you must use the double square brackets and not the single square brackets. The double square brackets mean you're doing a pattern match operation when you use == or =. If you use single square brackets:
if [ "$line" = *"$word"* ]
You're doing equality. Note that double square brackets don't need quotation marks while single brackets it is required in most situations.
echo $line | grep "$word"
would be the typical way to do this in a script, of course it does cost a new process
You can use the bash match operator =~:
[[ "$line" =~ "$word" ]] && echo "$line"
Don't forget quotes, as stated in previous answers (especially the one of #Bill).
The reason that if [ "$line"==*"$word"* ] does not work as you expect is perhaps a bit obscure. Assuming that no files exist that cause the glob to expand, then you are merely testing that the string 1234 abc xyz 5678==*1234* is non empty. Clearly, that is not an empty string, so the condition is always true. You need to put whitespace around your == operator, but that will not work because you are now testing if the string 1234 abc xyz 5678 is the same as the string to which the glob *1234* expands, so it will be true only if a file named 1234 abc xyz 5678 exists in the current working directory of the process executing the shell script. There are shell extensions that allow this sort of comparison, but grep works well, or you can use a case statement:
case "$line" in
*$word*) echo $line;;
esac
An alternative solution would be using loop:
for w in $line
do
if [ "$w" == "$word" ]; then
echo $line
break
fi
done
Code Snippet:
$a='text'
$b='text'
if [ $a -eq $b ]
then
msg='equal'
fi

Resources