bash verbose string comparison slashes - bash

I am comparing two strings in a bash script as follows:
x="hello"
y="hello"
if [[ "$x" != "$y" ]]; then
echo "different"
else
echo "same"
fi
This comparison works. When I execute the script with -x, the comparison still works, but it shows the output
+ x=hello
+ y=hello
+ [[ -n hello ]]
+ [[ hello != \h\e\l\l\o ]]
+ echo same
I'm curious why the right side of the string shows as\h\e\l\l\o and not hello

The simple explanation is for the same reason that the left-hand side doesn't have quotes around it.
-x is showing you an equivalent but not exact representation of what it ran. The right-hand side of = and != in [[ ... ]] is matched as a pattern.
From the manual:
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.
The -x output, for some reason, chooses to use escaping instead quoting to disable pattern matching there.

When using =, ==, and != in [[, the right-side string can contain globs (*, ?, etc.).
The backslashes in your example aren't necessary, though they don't hurt. They are needed if the right-side string contains a possible wildcard character. For example:
$ set -x
$ [[ hi == 'hi*' ]]; echo $?
+ [[ hi == \h\i\* ]]
+ echo 1
1
$ [[ hi == hi* ]]; echo $?
+ [[ hi == hi* ]]
+ echo 0
0

Related

Why is empty string changed into -n expression in bash

Taken this snippet:
$ [[ ""=="foo" ]] && echo yes || echo no
+ [[ -n ==foo ]]
+ echo yes
yes
How does [[ ""=="foo" ]] turn into [[ -n ==foo ]] ?
The RC was of course missing spaces around == - after adding them, it works as expected:
$ [[ "" == "foo" ]] && echo yes || echo no
+ [[ '' == \f\o\o ]]
+ echo no
no
But still i cannot understand why it behaved like this?
It's not changing the empty string into -n.
The string ""=="foo" is equivalent to the string ==foo. The trace output always shows strings in their simplest format, without unnecessary quotes.
A conditional expression that just contains a single string with no operators is true if the string is not empty. That's what the -n operator tests, so the -x expansion shows it that way.
Any operand that isn't preceded or followed by an operator is treated to have an equal operation as -n <operand>. Operators also need to be isolated with spaces to be distinguished. For a list of operators run help test. Also run help [[ to see how the keyword is different from the [ and test builtins.

In Bash, is it possible to match a string variable containing wildcards to another string

I am trying to compare strings against a list of other strings read from a file.
However some of the strings in the file contain wildcard characters (both ? and *) which need to be taken into account when matching.
I am probably missing something but I am unable to see how to do it
Eg.
I have strings from file in an array which could be anything alphanumeric (and include commas and full stops) with wildcards : (a?cd, xy, q?hz, j,h-??)
and I have another string I wish to compare with each item in the list in turn. Any of the strings may contain spaces.
so what I want is something like
teststring="abcdx.rubb ish,y"
matchstrings=("a?cd" "*x*y" "q?h*z" "j*,h-??")
for i in "${matchstrings[#]}" ; do
if [[ "$i" == "$teststring" ]]; then # this test here is the problem
<do something>
else
<do something else>
fi
done
This should match on the second "matchstring" but not any others
Any help appreciated
Yes; you just have the two operands to == reversed; the glob goes on the right (and must not be quoted):
if [[ $teststring == $i ]]; then
Example:
$ i=f*
$ [[ foo == $i ]] && echo pattern match
pattern match
If you quote the parameter expansion, the operation is treated as a literal string comparison, not a pattern match.
$ [[ foo == "$i" ]] || echo "foo != f*"
foo != f*
Spaces in the pattern are not a problem:
$ i="foo b*"
$ [[ "foo bar" == $i ]] && echo pattern match
pattern match
You can do this even completely within POSIX, since case alternatives undergo parameter substitution:
#!/bin/sh
teststring="abcdx.rubbish,y"
while IFS= read -r matchstring; do
case $teststring in
($matchstring) echo "$matchstring";;
esac
done << "EOF"
a?cd
*x*y
q?h*z
j*,h-??
EOF
This outputs only *x*y as desired.

Regular expression in bash not working in conditional construct in Bash with operator '=~'

The regular expression I have put into the conditional construct (with the =~ operator) would not return the value as I had expected, but when I assign them into two variables it worked. Wondering if I had done something wrong.
Version 1 (this one worked)
a=30
b='^[0-9]+$' #pattern looking for a number
[[ $a =~ $b ]]
echo $?
#result is 0, as expected
Version 2 (this one doesn't work but I thought it is identical)
[[ 30 =~ '^[0-9]+$' ]]
echo $?
#result is 1
Don't quote the regular expression:
[[ 30 =~ ^[0-9]+$ ]]
echo $?
From the manual:
Any part of the pattern may be quoted to force the quoted portion to be matched as a string.
So if you quote the entire pattern, it's treated as a fixed string match rather than a regular expression.

How can I check if variable equal to other variable apart of (maybe) the last letter?

Given the following variable:
var=abcd
What I need to write in the if so that I will get true if I will compare var with other variables so that they with beginning of abcd but maybe they with (at most) more a letter (in the last) ?
for example:
I want the following cases will give me TRUE:
var in related to abcd.
var in related to abcde.
var in related to abcdk.
And the following cases will give me FALSE:
var in related to abc.
var in related to abcdee.
With pure POSIX, you can check if the variable itself is equal to abcd, or if the variable minus its last letter is equal.
if [ "$var" = abcd ] || [ "${var%?}" = abcd ]; then
Using bash's [[ ... ]], you can use either extended pattern matching
if [[ $var = abcd#(|?) ]]; then
(where #(|?) matches exactly one of the empty string or a single arbitrary character)
or a regular expression match
if [[ $var ~= ^abcd.?$ ]]; then
(where .? matches a single optional character. ^ and $ match the beginning and end, respectively, of the string, limiting the length of the match).
The [[ command in Bash does pattern matching on strings by default, so you can use ?, * and some other metacharacters for matching one (any, but exactly one) with ?, zero or more characters with *, etc. For example:
$ [[ abcde == abcd? ]] && echo yes || echo no
yes
$ [[ abcdefg == abcd* ]] && echo yes || echo no
yes
$ [[ abc == abcd? ]] && echo yes || echo no
no
$ [[ abcd == abcd? ]] && echo yes || echo no
no
Note that the value is on the left, and the pattern on the right. Also, [[ supports the use of logical operators like || and &&, so you can combine it like:
ref=abcd
val=abcde
if [[ $val == $ref || $val == $ref? ]]; then
# match
fi
You have an additional option using string indexes in bash. You can simply check whether the first 4 letters of a variable match abcd and check whether the variable length is greater than 5, e.g.
if [[ abcd = ${var:0:4} ]] && [[ ${#var} -le 5 ]]; then
## condition true
fi
You have a number of options in bash. You can even make the testing above generic by storing your test string in a variable, and then using the length in place of each of the number above, e.g.
test_string="abcd"
len=${#test_string}
if [[ $test_string = ${var:0:$((len))} ]] && [[ ${#var} -le $((len+1)) ]]; then
## condition true
fi

Detecting when a string exists but doesn't start with - in bash

I am trying to make a bash program that saves results to a file with the name of the user's choosing if the program is supplied the --file argument followed by an option, in which the option should not start with a dash. So I used the following conditional:
if [[ -n $2 && !($2="[^-]") ]]
But that didn't work. It still saves the output to a file even if the second argument starts with a dash. I also tried using this:
1) if ! [[ -z $2 && ($2="[^-]") ]]
It also did as the previous one. What's the problem? Thanks in advance!
As a pattern match, this might look like:
[[ $2 ]] && [[ $2 != -* ]]
Note:
Moving && outside of [[ ]] isn't mandatory, but it is good form: It ensures that your code can be rewritten to work with the POSIX test command without either using obsolescent functionality (-a and -o) or needing to restructure.
Whitespace is mandatory. In !($2="[^-]"), neither the ! nor the ( and ) nor the = are parsed as separate operators.
= and != check for pattern matches, not regular expressions. The regular expression operator in [[ ]] is =~. Among the differences, anchors (^ to match at the beginning of a string, or $ to match at the end) are implicit in a pattern whereas they need to be explicit in a regex, and * has a very different meaning (* in a pattern means the same thing as .* in a regex).
The ^ in [^-] already negates the -, so by using ! in addition, you're making your code only match when there is a dash in the second argument.
To test this yourself:
$ check_args() { [[ $2 ]] && [[ $2 != -* ]]; echo $?; }
$ check_args one --two
1
$ check_args one two
0
$ check_args one
1

Resources