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

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 =~

Related

Bash how to check if string contains '*'? [duplicate]

This question already has answers here:
How to check if string contains characters in regex pattern in shell?
(3 answers)
Closed 4 months ago.
Bash how to check whether a string in bash contains *. Since * is a special character?
if [[ $string == *"*"* ]]; then echo "string contains asterisk"; fi
Within double braces, the == operator does pattern matching. Quoted parts of a pattern are handled as literal strings.
With a regex, * is not special inside a bracket expression, or when quoted:
if [[ $string =~ [*] ]]; then echo "string contains asterisk"; fi
if [[ $string =~ "*" ]]; then echo "string contains asterisk"; fi
You can use grep "\*" filename if you need to check if a string within a file contains a "*".
As * is a special character it is then necessary to use the escape character \.

The escape character `\` in bracket expression

I read such an example which excerpted from an instruction,
and intend to examine \ in ^[-[:alnum:]\._]+$
# is input a valid filename?
read -p "Enter a single item > "
if [[ "$REPLY" =~ ^[-[:alnum:]\._]+$ ]]; then
echo "'$REPLY' is a valid filename."
else
echo "The string '$REPLY' is not a valid filename."
fi
check the bracket expression by feeding some combinations.
$ bash read_validate.sh
Enter a single item > test.tst.
'test.tst.' is a valid filename.'
#test `\`
$ bash read_validate.sh
Enter a single item > test\\tst
The string 'test\tst' is not a valid filename.
When i remove the escape \ from ^[-[:alnum:]\._]+$, to be ^[-[:alnum:]._]+$
$ bash read_validate.sh
Enter a single item > test.tst
'test.tst' is a valid filename.
# to assert that dot is not the any character sign.
$ bash read_validate.sh
Enter a single item > test*tst
The string 'test*tst' is not a valid filename.
# codes run properly.
It seems not necessary to insert escape \ to the pattern.
Is that right?
I cannot make sure if omit some key points about the bracket expression and escape character?
Bash uses Extended Regular Expressions. Quoting the standard:
The special characters '.', '*', '[', and '\' (period, asterisk, left-bracket, and backslash, respectively) shall lose their special meaning within a bracket expression.
So inside [ ], they don't need to be escaped.
The situation is made slightly more complicated by the fact that Bash processes backslashes in your string:
$ set -x
$ [[ '\' =~ [\.] ]] && echo yes
+ [[ \ =~ [.] ]] # look, no backslash!
So the recommended way to use regular expressions is to set a shell variable:
$ re='[\.]'
+ re='[\.]'
$ [[ '\' =~ $re ]] && echo yes
+ [[ \ =~ [\.] ]] # backslash preserved!
+ echo yes
yes

Different behavior on escaping double quotes

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.

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.

Why doesn't [[ ... ]] work when script called with sh, while [ ... ] works always?

Script test.sh:
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "no arg"
else
echo "have arg"
fi
When I ran it as
./test.sh
it said "no arg", which was expected, but if I run it as
sh ./test.sh
it prints "have arg", but it you print $#, it's zero in both cases.
However, the script below
#!/bin/bash
if [ $# -eq 0 ]; then
echo "no arg"
else
echo "have arg"
fi
prints "no arg" in both cases.
Could somebody explain this? Why does [[ ... ]] interpret $# differently from [ ... ]?
The explanations I've read about [[ ... ]] weren't clear enough about this.
/bin/sh is often a different shell interpreter than bash. On my ubuntu system, it's a symlink to dash. The different shells have different syntax.
[ foo ] and test foo are equivalent in both bash and dash.
[[ foo ]] is a bash expression that is similar to [ ] tests, but with some differences that are noted in man bash.
Dash does not have a [[ command, which results in an error-exitcode, and the execution of the else branch.
[[ expression ]]
Return a status of 0 or 1 depending on the evaluation of the
conditional expression expression. Expressions are composed of
the primaries described below under CONDITIONAL EXPRESSIONS.
Word splitting and pathname expansion are not performed on the
words between the [[ and ]]; tilde expansion, parameter and
variable expansion, arithmetic expansion, command substitution,
process substitution, and quote removal are performed. Condi‐
tional operators such as -f must be unquoted to be recognized as
primaries.
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. If the shell
option nocasematch is enabled, the match is performed without
regard to the case of alphabetic characters. The return value
is 0 if the string matches (==) or does not match (!=) the pat‐
tern, and 1 otherwise. Any part of the pattern may be quoted to
force it to be matched as a string.
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 expres‐
sion and matched accordingly (as in regex(3)). The return value
is 0 if the string matches the pattern, and 1 otherwise. If the
regular expression is syntactically incorrect, the conditional
expression's return value is 2. If the shell option nocasematch
is enabled, the match is performed without regard to the case of
alphabetic characters. Any part of the pattern may be quoted to
force it to be matched as a string. Substrings matched by
parenthesized subexpressions within the regular expression are
saved in the array variable BASH_REMATCH. The element of
BASH_REMATCH with index 0 is the portion of the string matching
the entire regular expression. The element of BASH_REMATCH with
index n is the portion of the string matching the nth parenthe‐
sized subexpression.
In my case (ubuntu 9.04) I also see the following error line ABOVE the "have arg" line:
./test.sh: 6: [[: not found
And indeed that's as it should be; /bin/sh is a symbolic link to 'dash' not 'bash'.

Resources