How to check if a string contains a substring in Bash
How to check if a string contains a substring in Bash
Question
I have a string in Bash:
string="My string"
How can I test if it contains another string?
if [ $string ?? 'foo' ]; then
echo "It's there!"
fi
Where ??
is my unknown operator. Do I use echo and grep
?
if echo "$string" | grep 'foo'; then
echo "It's there!"
fi
That looks a bit clumsy.
Accepted Answer
You can use Marcus's answer (* wildcards) outside a case statement, too, if you use double brackets:
string='My long string'
if [[ $string == *"My long"* ]]; then
echo "It's there!"
fi
Note that spaces in the needle string need to be placed between double quotes, and the *
wildcards should be outside. Also note that a simple comparison operator is used (i.e. ==
), not the regex operator =~
.
Read more... Read less...
If you prefer the regex approach:
string='My string';
if [[ $string =~ "My" ]]
then
echo "It's there!"
fi
I am not sure about using an if statement, but you can get a similar effect with a case statement:
case "$string" in
*foo*)
# Do stuff
;;
esac
stringContain
variants (compatible or case independent)
As these Stack Overflow answers tell mostly about Bash, I've posted a case independent Bash function at the very bottom of this post...
Anyway, there is my
Compatible answer
As there are already a lot of answers using Bash-specific features, there is a way working under poorer-featured shells, like BusyBox:
[ -z "${string##*$reqsubstr*}" ]
In practice, this could give:
string='echo "My string"'
for reqsubstr in 'o "M' 'alt' 'str';do
if [ -z "${string##*$reqsubstr*}" ] ;then
echo "String '$string' contain substring: '$reqsubstr'."
else
echo "String '$string' don't contain substring: '$reqsubstr'."
fi
done
This was tested under Bash, Dash, KornShell (ksh
) and ash (BusyBox), and the result is always:
String 'echo "My string"' contain substring: 'o "M'.
String 'echo "My string"' don't contain substring: 'alt'.
String 'echo "My string"' contain substring: 'str'.
Into one function
As asked by @EeroAaltonen here is a version of the same demo, tested under the same shells:
myfunc() {
reqsubstr="$1"
shift
string="[email protected]"
if [ -z "${string##*$reqsubstr*}" ] ;then
echo "String '$string' contain substring: '$reqsubstr'.";
else
echo "String '$string' don't contain substring: '$reqsubstr'."
fi
}
Then:
$ myfunc 'o "M' 'echo "My String"'
String 'echo "My String"' contain substring 'o "M'.
$ myfunc 'alt' 'echo "My String"'
String 'echo "My String"' don't contain substring 'alt'.
Notice: you have to escape or double enclose quotes and/or double quotes:
$ myfunc 'o "M' echo "My String"
String 'echo My String' don't contain substring: 'o "M'.
$ myfunc 'o "M' echo \"My String\"
String 'echo "My String"' contain substring: 'o "M'.
Simple function
This was tested under BusyBox, Dash, and, of course Bash:
stringContain() { [ -z "${2##*$1*}" ]; }
Then now:
$ if stringContain 'o "M3' 'echo "My String"';then echo yes;else echo no;fi
no
$ if stringContain 'o "M' 'echo "My String"';then echo yes;else echo no;fi
yes
... Or if the submitted string could be empty, as pointed out by @Sjlver, the function would become:
stringContain() { [ -z "${2##*$1*}" ] && [ -z "$1" -o -n "$2" ]; }
or as suggested by Adrian Günter's comment, avoiding -o
switches:
stringContain() { [ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ];};}
Final (simple) function:
And inverting the tests to make them potentially quicker:
stringContain() { [ -z "$1" ] || { [ -z "${2##*$1*}" ] && [ -n "$2" ];};}
With empty strings:
$ if stringContain '' ''; then echo yes; else echo no; fi
yes
$ if stringContain 'o "M' ''; then echo yes; else echo no; fi
no
Case independent (Bash only!)
For testing strings without care of case, simply convert each string to lower case:
stringContain() {
local _lc=${2,,}
[ -z "$1" ] || { [ -z "${_lc##*${1,,}*}" ] && [ -n "$2" ] ;} ;}
Check:
stringContain 'o "M3' 'echo "my string"' && echo yes || echo no
no
stringContain 'o "My' 'echo "my string"' && echo yes || echo no
yes
if stringContain '' ''; then echo yes; else echo no; fi
yes
if stringContain 'o "M' ''; then echo yes; else echo no; fi
no
You should remember that shell scripting is less of a language and more of a collection of commands. Instinctively you think that this "language" requires you to follow an if
with a [
or a [[
. Both of those are just commands that return an exit status indicating success or failure (just like every other command). For that reason I'd use grep
, and not the [
command.
Just do:
if grep -q foo <<<"$string"; then
echo "It's there"
fi
Now that you are thinking of if
as testing the exit status of the command that follows it (complete with semi-colon), why not reconsider the source of the string you are testing?
## Instead of this
filetype="$(file -b "$1")"
if grep -q "tar archive" <<<"$filetype"; then
#...
## Simply do this
if file -b "$1" | grep -q "tar archive"; then
#...
The -q
option makes grep not output anything, as we only want the return code. <<<
makes the shell expand the next word and use it as the input to the command, a one-line version of the <<
here document (I'm not sure whether this is standard or a Bashism).
The accepted answer is best, but since there's more than one way to do it, here's another solution:
if [ "$string" != "${string/foo/}" ]; then
echo "It's there!"
fi
${var/search/replace}
is $var
with the first instance of search
replaced by replace
, if it is found (it doesn't change $var
). If you try to replace foo
by nothing, and the string has changed, then obviously foo
was found.