The vim help has the following description.
https://vimhelp.org/eval.txt.html#expr4
https://vimhelp.org/eval.txt.html#expr4:~:text=%22foo%5Cnbar%22%20%3D~%20%22%5Cn%22%20%20%20%20%20%20evaluates%20to%201%0A%20%20%20%20%20%20%20%20%22foo%5Cnbar%22%20%3D~%20%22%5C%5Cn%22%20%20%20%20%20evaluates%20to%200
"foo\nbar" =~ "\n" evaluates to 1
"foo\nbar" =~ "\\n" evaluates to 0
But, when we actually tried it, we got the following results.
test.vim
echo "foo\nbar" =~ "\n"
echo "foo\nbar" =~ "\\n"
execute :source test.vim
1
1
How did it end up like this?
Related
I was trying to check if an array contained a value and I found Check if a Bash array contains a value
It did not work for me, and it was because I decided to remove the spaces surrounding the quotes, like this:
words=(aa bb cc)
[[ " ${words[#]} " =~ " a " ]] && echo "YES" || echo "NO"; # This is the real answer, and it works
------
[[ "${words[#]}" =~ "a" ]] && echo "YES" || echo "NO"; # This does not work. Why???
Whats the difference when you surround them with spaces or when you dont?
And for my curiosity. In the previous question I mentioned, some answers/comments go with:
${array[*]}, and others with ${array[#]}
Do they both "iterate" through the loop in the same way?
[[ string =~ regex ]] checks if regex matches any substring of string. That operator does not iterate over your array entries as for entry in "${words[#]}" would do. It cannot even handle arrays.
[[ " ${words[#]} " =~ " a " ]] is only a hack for finding array elements. The array is converted into a single string where all array entries are surrounded by spaces. Then, we search for an element using a string search. This only works reliably, if the array entries themselves do not contain any spaces.
For the difference between ${array[*]} and ${array[#]} see bash's manual:
If the word is double-quoted, ${name[*]} expands to a single word with the value of each array member separated by the first character of the IFS variable [= a space by default], and ${name[#]} expands each element of name to a separate word.
So in this case, * would be the logical way to write [[ " ${words[*]} " =~ " a " ]], because it is equivalent to [[ " aa bb cc " =~ " a " ]].
That # works here too is somewhat strange, because [[ " aa" "bb" "cc " =~ " a " ]] gives a syntax error. Bash tolerating the misused # in [[ is probably an undocumented "feature" related to the (documented) disabled word-splitting inside [[. For the basic POSIX test command [ the difference between * and # works as expected, meaning a=("x" "=" "y"); [ "${a[#]}" ] fails because x != y.
This question already has answers here:
When to wrap quotes around a shell variable?
(5 answers)
Closed 2 years ago.
Consider this piece of code:
#!/bin/bash +x
echo -n "String: "
read s
n=`expr index $s " "`
if [ $n -gt 0 ]; then
m=`expr $n - 1`
echo "Nome: " `expr substr $s 1 $m`
fi
When I run it with the and write "John Smith" in the prompt, I get this error:
./script.sh: line 5: [: -gt: unary operator expected
I can fix it by involving the $s on the definition of n and in the echo command in double quotes, as such:
#!/bin/bash +x
echo -n "String: "
read s
n=`expr index "$s" " "`
if [ $n -gt 0 ]; then
m=`expr $n - 1`
echo "Nome: " `expr substr "$s" 1 $m`
fi
This bottom one works just fine. But why? What difference does the " " make?
Without the double quotes, your expr command is:
expr index John Smith " "
That reports a syntax error, because the index operator should be followed by only two arguments, but you gave it three arguments. Since it gets an error, it doesn't output a result, so $n is set to an empty string. Then the if command becomes
if [ -gt 0 ]
which is missing one of the operands.
Motto: always quote variables unless you need the value to undergo word splitting or globbing.
[[ " stop start status " =~ " $2 " && (($#<3)) ]] || { echo "Usage $0 file_name command"; exit 1;}
I frequently use the above solution to check the input range of my Bash script.
Now I realise that the extended arithmetic expression (()) looks like it is suppressed inside the double bracket [[]].
To illustrate the problem:
a=start; n=1; [[ " stop start status " =~ " $a " && (($n<3)) ]] && echo ok || echo bad
ok
a=start; n=5; [[ " stop start status " =~ " $a " && (($n<3)) ]] && echo ok || echo bad
bad
# But:
a=start; n=100; [[ " stop start status " =~ " $a " && (($n<3)) ]] && echo ok || echo bad
ok
The above result is false because n not less than 3 if they are treated as numbers.
This is the correct solution:
a=start; n=100; [[ " stop start status " =~ " $a " ]] && (($n<3)) && echo ok || echo bad
bad
a=start; n=1; [[ " stop start status " =~ " $a " ]] && (($n<3)) && echo ok || echo bad
ok
The GNU bash man page for [[..]] explains that the operator runs a conditional expression and
Return a status of 0 or 1 depending on the evaluation of the conditional expression expression. Expressions are composed of the primaries described below in Bash Conditional Expressions.
But the arithmetic operator is not part of the supported conditional expressions (primaries) inside [[..]] which means the expression is forced to run as a string comparison, i.e.
(( $n < 3))
is not run in arithmetic context but just as plain lexicographic (string) comparison as
[[ 100 < 3 ]]
which will always result true, because the ASCII values for 1, 0, 0 appear before 3
But inside [[..]] arithmetic operations are supported if you use -lt, -gt
arg1 OP arg2
OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively.
So had you written your expression as
a=start; n=100; [[ " stop start status " =~ " $a " && $n -lt 3 ]] && echo ok || echo bad
bad
it would have worked as expected.
Or even if you had forced the arithmetic expression usage by prefixing $ before ((..)) and written it as below (note that bash does not have documented behavior for $((..)) inside [[..]]). The likely expected behavior is the arithmetic expression is expanded before the [[..]] is evaluated and the resultant output is evaluated in a string context as [[ 0 ]] which means a non-empty string.
a=start; n=5; [[ " stop start status " =~ " $a " && $(( $n < 3 )) ]] && echo ok || echo bad
The result would still look bad, because the arithmetic expression inside [[..]] decomposes into an unary string not empty comparison expression as
$(( 5 < 3 ))
0
[[ -n 0 ]]
The result of the arithmetic evaluation 0 (false) is taken as a non-zero entity by the test operator and asserts true on the right-side of &&. The same would apply for the other case also e.g. say n=1
$(( 1 < 3 ))
1
[[ -n 1 ]]
So long story short, use the right operands for arithmetic operation inside [[..]].
(( is a "keyword" that introduces the arithmetic statement. Inside [[, however, you can't use other statements. You can use parentheses to group expressions though, so that's what (( ... )) is: a redundant "double group". The following are all equivalent, due to the precedences of < and &&:
[[ " stop start status " =~ " $2 " && (($#<3)) ]]
[[ " stop start status " =~ " $2 " && ($#<3) ]]
[[ " stop start status " =~ " $2 " && $#<3 ]]
If you want integer comparison, use -lt instead of <, but you also don't need to fit everything inside [[ ... ]]. You can use a conditional statement and an arithmetic statement together in a command list.
{ [[ " stop start status " =~ " $2 " ]] && (($#<3)) ; } || { echo "Usage $0 file_name command"; exit 1;}
In this case, ... && ... || ... will work the way you expect, though in general that is not the case. Prefer an if statement instead.
if [[ " stop start status " =~ " $2 " ]] && (($#<3)); then
echo "Usage $0 file_name command"
exit 1
fi
Curious to know why the following is not working for character: +
Characters "\", "(" and "*" makes sense i.e. * will expand to folders/files in current directory (during command line shell expansion) and similarly \ and ( will expect closing character to work but my understanding was "+" should have worked like "-" did.
PS: I know putting double quotes i.e. "${o}" in the IF statement, will work for all characters in my test case below. Using \${o} in IF statement with or without double quote will fail all the checks.
$ for o in - + \` ~ \~ , _ = / \\ ! # \# $ \$ % ^ \& \* \( \); do a="a${o}b${o}c";if [[ $a =~ ${o} ]]; then echo "${o} exists in $a and =~ works"; else echo -e "\ncharacter ${o} doesn't work with =~\n"; fi; done
- exists in a-b-c and =~ works
character + doesn't work with =~
` exists in a`b`c and =~ works
/home/ubuntu exists in a/home/ubuntub/home/ubuntuc and =~ works
~ exists in a~b~c and =~ works
, exists in a,b,c and =~ works
_ exists in a_b_c and =~ works
= exists in a=b=c and =~ works
/ exists in a/b/c and =~ works
character \ doesn't work with =~
! exists in a!b!c and =~ works
# exists in a#b#c and =~ works
# exists in a#b#c and =~ works
$ exists in a$b$c and =~ works
$ exists in a$b$c and =~ works
% exists in a%b%c and =~ works
^ exists in a^b^c and =~ works
& exists in a&b&c and =~ works
character * doesn't work with =~
character ( doesn't work with =~
) exists in a)b)c and =~ works
The fundamental misunderstanding behind this question is that =~ is a substring-search operator. It is not.
The right-hand side of =~ is evaluated as a POSIX ERE expression. =~ is thus a regex-matching operator, which happens to be frequently used for searches when the right-hand side is quoted to make its contents literal (or when this string is known to match only itself when interpreted as an ERE).
+, in regex, means "1-or-more of the preceding token" -- just as * means "0-or-more of the preceding token".
Thus, either [[ $foo =~ + ]] or [[ $foo =~ * ]] makes no sense, because these are checking for zero-or-more of a preceding token that doesn't exist at all.
Similarly, ( and ) have meaning in ERE as the beginning and end of a match group, so when they're given bare (unescaped/unquoted), they result in an invalid regex.
If you quote the expansion, by contrast, all characters contained will be treated as literal, rather than being treated as regular expression metacharacters, thus resulting in the presumably-intended behavior.
If you want to check whether a literal character is contained in a string, either quote it -- [[ $foo =~ "$o" ]] -- or use a glob-style pattern: [[ $foo = *"$o"* ]]
As the title said what does the "=~" do in the bash shell script running on Linux? I googled online and found that "==" checks equality and "!=" checks inequality. How about "=~"? I guess it may be some regular expression matching?
The =~ does a bash regular expression match
Example
$ [[ 45 =~ [0-9]+ ]] && echo "45 contains digits"
45 contains digits
$ [[ "hello" =~ [0-9]+ ]] && echo "hello doesnt contains digits"
$ [[ "hello" =~ [a-z]+ ]] && echo "hello contains alphabets"
hello contains alphabets
Yes, it's regular expression matching. It's in the bash manual:
An additional binary operator, =~, is available, with the same precedence as == and !=. When it is used, the string to the right of the operator is considered an extended regular expression and matched accordingly (as in regex3)).