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.
expr
command here - anyone 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 =~
.
Answered 2023-09-20 20:16:44
[[: not found
. Any idea what's wrong? I'm using GNU bash, version 4.1.5(1), on Ubuntu. - anyone #!/bin/sh
. Try #!/bin/bash
instead. - anyone [
operator (the double brackets [[
are probably mandatory with wildcards) - anyone sh
in many docker containers. - anyone If you prefer the regex approach:
string='My string';
if [[ $string =~ "My" ]]; then
echo "It's there!"
fi
Answered 2023-09-20 20:16:44
=~
operator already searches the whole string for a match; the .*
's here are extraneous. Also, quotes are generally preferable to backslashes: [[ $string =~ "My s" ]]
- anyone E14)
. It's probably best to assign to a variable (using quotes), then compare. Like this: re="My s"; if [[ $string =~ $re ]]
- anyone if [[ ! "abc" =~ "d" ]]
is true. - anyone 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
Answered 2023-09-20 20:16:44
[[ $string == *foo* ]]
also works in some POSIX compliant sh versions (e.g. /usr/xpg4/bin/sh
on Solaris 10) and ksh (>= 88) - anyone [[ $string == *foo* ]]
will not work. - anyone Full rewrite 2023-07-03!!
bash
case independent, hints and remarks.Previous answer was based on parameter expansion, but after doing comparison with case based solution, as proposed by Marcus Griep's answer, I have to confess: case method is a lot more efficient!
case $string in
*$substring* )
do something with "$substring"
;;
esac
As a function:
stringContain() { case $2 in *$1* ) return 0;; *) return 1;; esac ;}
for string in 'echo "My string"' "Don't miss quotes" ''; do # 3 strings
for substr in "'t mis" 'o "My' "s" "Y" ""; do # 5 substrings
if stringContain "$substr" "$string"; then
printf 'Match: %-12s %s\n' "'$substr'" "'$string'"
else
printf 'No match: %s\n' "'$substr'"
fi
done
done
No match: ''t mis'
Match: 'o "My' 'echo "My string"'
Match: 's' 'echo "My string"'
No match: 'Y'
Match: '' 'echo "My string"'
Match: ''t mis' 'Don't miss quotes'
No match: 'o "My'
Match: 's' 'Don't miss quotes'
No match: 'Y'
Match: '' 'Don't miss quotes'
No match: ''t mis'
No match: 'o "My'
No match: 's'
No match: 'Y'
Match: '' ''
In previous answer I'd proposed:
stringContain() { [ -z "$1" ] || { [ -z "${2##*$1*}" ] && [ -n "$2" ];};}
But after doing some comparisons, using dash
, busybox shell
, bash
and ksh
, here is my average result:
Comparing time PExp vs Case method under bash : 634.71%
Comparing time PExp vs Case method under dash : 878.87%
Comparing time PExp vs Case method under ksh : 217.95%
Comparing time PExp vs Case method under busybox : 752.42%
Full test script: stringContain-test.sh
case
method is at least 2 time quicker thanparameter expansion
method regardless shell implementation used.
From this point of view, this seem easy to understand that case
method is more efficient!
Under bash and some other shell, you could use parameter expansion to quickly transform your string to lower or upper case, by using respectively: ${var,,}
and ${var^^}
:
So adding -i
option to function, for case independent, could be done by:
stringContain() {
if [[ $1 == -i ]] ; then
case ${3,,} in
*${2,,}*) return 0;;
*) return 1;;
esac
else
case $2 in
*$1*) return 0;;
*) return 1;;
esac
fi
}
stringContain hello 'Hello world!' && echo yes || echo no
no
stringContain -i hello 'Hello world!' && echo yes || echo no
yes
Answered 2023-09-20 20:16:44
string_contains() { [ -z "${2##*$1*}" ] && [ -n "$2" -o -z "$1" ]; }
A final thought: does the empty string contain the empty string? The version above things yes (because of the -o -z "$1"
part). - anyone 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).
Answered 2023-09-20 20:16:44
if grep -q foo <(echo somefoothing); then
- anyone echo
alone is pretty portable. The flags are not. If you find yourself thinking about -e
or -n
use printf
- anyone grep -q foo <<<"$mystring"
implie 1 fork and is bashism and echo $mystring | grep -q foo
implie 2 forks (one for the pipe and the second for running /path/to/grep
) - anyone echo
without flags might still have unexpected portability problems if the argument string contains backslash sequences. echo "nope\c"
is expected on some platforms to work like echo -e "nope"
on some others. printf '%s' "nope"
vs printf '%s\n' 'nope\c'
- anyone 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.
Answered 2023-09-20 20:16:44
$XDG_CURRENT_DESKTOP
to $string
. The expression you want is if [ "$XDG_CURRENT_DESKTOP" != "${XDG_CURRENT_DESKTOP/GNOME/}" ]; then echo MATCHES GNOME; fi
- anyone "x$string" != "x${string/foo/}"
is better. - anyone So there are lots of useful solutions to the question - but which is fastest / uses the fewest resources?
Repeated tests using this frame:
/usr/bin/time bash -c 'a=two;b=onetwothree; x=100000; while [ $x -gt 0 ]; do TEST ; x=$(($x-1)); done'
Replacing TEST each time:
[[ $b =~ $a ]] 2.92 user 0.06 system 0:02.99 elapsed 99% CPU
[ "${b/$a//}" = "$b" ] 3.16 user 0.07 system 0:03.25 elapsed 99% CPU
[[ $b == *$a* ]] 1.85 user 0.04 system 0:01.90 elapsed 99% CPU
case $b in *$a):;;esac 1.80 user 0.02 system 0:01.83 elapsed 99% CPU
doContain $a $b 4.27 user 0.11 system 0:04.41 elapsed 99%CPU
(doContain was in F. Houri's answer)
And for giggles:
echo $b|grep -q $a 12.68 user 30.86 system 3:42.40 elapsed 19% CPU !ouch!
So the simple substitution option predictably wins whether in an extended test or a case. The case is portable.
Piping out to 100000 greps is predictably painful! The old rule about using external utilities without need holds true.
Answered 2023-09-20 20:16:44
[[ $b == *$a* ]]
. - anyone case
wins with the smallest overall time consumption. You are missing an asterisk after $b in *$a
though. I get slightly faster results for [[ $b == *$a* ]]
than for case
with the bug corrected, but it could depend on other factors too, of course. - anyone [[ $b == *$a* ]]
is quick and case
is almost as quick (and pleasantly POSIX-compatible). - anyone [[ $b == *$a* ]]
and the case statement case $b in *$a):;;esac
are not equivalent in a no-match condition. Swapping $a
and $b
results in exit code 1 for the conditional expression [[
and exit code 0 for the case
statement. As per help case
: Exit Status: Returns the status of the last command executed. The return status is zero if no pattern is matched, which is probably not the expected behavior. To return 1 in the no match condition, it should be: case $b in *$a*):;; *) false ;; esac
- anyone Bash 4+ examples. Note: not using quotes will cause issues when words contain spaces, etc. Always quote in Bash, IMO.
Here are some examples Bash 4+:
Example 1, check for 'yes' in string (case insensitive):
if [[ "${str,,}" == *"yes"* ]] ;then
Example 2, check for 'yes' in string (case insensitive):
if [[ "$(echo "$str" | tr '[:upper:]' '[:lower:]')" == *"yes"* ]] ;then
Example 3, check for 'yes' in string (case sensitive):
if [[ "${str}" == *"yes"* ]] ;then
Example 4, check for 'yes' in string (case sensitive):
if [[ "${str}" =~ "yes" ]] ;then
Example 5, exact match (case sensitive):
if [[ "${str}" == "yes" ]] ;then
Example 6, exact match (case insensitive):
if [[ "${str,,}" == "yes" ]] ;then
Example 7, exact match:
if [ "$a" = "$b" ] ;then
Example 8, wildcard match .ext (case insensitive):
if echo "$a" | egrep -iq "\.(mp[3-4]|txt|css|jpg|png)" ; then
Example 9, use grep on a string case sensitive:
if echo "SomeString" | grep -q "String"; then
Example 10, use grep on a string case insensitive:
if echo "SomeString" | grep -iq "string"; then
Example 11, use grep on a string case insensitive w/ wildcard:
if echo "SomeString" | grep -iq "Some.*ing"; then
Example 12, use doublehash to compare (if variable empty could cause false postitives etc) (case sensitive):
if [[ ! ${str##*$substr*} ]] ;then #found
Enjoy.
Answered 2023-09-20 20:16:44
${str,,}
convert $str
to lower case. Great solutions / great list! - anyone ${str}
if I'm testing a variable? Does ${$MYVAR,,}
work? bash says bad substitution
- anyone This also works:
if printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
printf "Found needle in haystack"
fi
And the negative test is:
if ! printf -- '%s' "$haystack" | egrep -q -- "$needle"
then
echo "Did not find needle in haystack"
fi
I suppose this style is a bit more classic -- less dependent upon features of Bash shell.
The --
argument is pure POSIX paranoia, used to protected against input strings similar to options, such as --abc
or -a
.
Note: In a tight loop this code will be much slower than using internal Bash shell features, as one (or two) separate processes will be created and connected via pipes.
Answered 2023-09-20 20:16:44
echo
is unportable, you should be using printf '%s' "$haystack
instead. - anyone echo
altogether for anything but literal text without escapes that doesn't start with a -
. It may work for you, but it's not portable. Even bash's echo
will behave differently depending on whether the xpg_echo
option is set. P.S.: I forgot to close the double quote in my previous comment. - anyone --
is not listed in the POSIX spec for printf
, but you should use printf '%s' "$anything"
anyway, to avoid issues if $anything
contains a %
character. - anyone As Paul mentioned in his performance comparison:
if echo "abcdefg" | grep -q "bcdef"; then
echo "String contains is true."
else
echo "String contains is not true."
fi
This is POSIX compliant like the 'case "$string" in' the answer provided by Marcus, but it is slightly easier to read than the case statement answer. Also note that this will be much much slower than using a case statement. As Paul pointed out, don't use it in a loop.
Answered 2023-09-20 20:16:44
How about this:
text=" <tag>bmnmn</tag> "
if [[ "$text" =~ "<tag>" ]]; then
echo "matched"
else
echo "not matched"
fi
Answered 2023-09-20 20:16:44
Accepted answer is correct but it is hard to read and understand.
For problems related to searching you should always use the $needle in a $haystack idiom.
Since its suggested edit queue is full, I post this:
haystack='There are needles here.'
if [[ "$haystack" == *"needle"* ]]; then
echo "It's there!"
fi
Answered 2023-09-20 20:16:44
bash
, and when this is used in a script file, it must start with the line #!/bin/bash
- anyone [[ $string == *foo* ]] && echo "It's there" || echo "Couldn't find"
Answered 2023-09-20 20:16:44
echo "Couldn't find
statement at the end is a nice trick to return 0 exit statuses for these matching commands. - anyone || echo "Couldn't find"
then you will return an error exit status if there is no match, which you might not want if you're running a CI pipeline for example where you want all commands to return non error exit statuses - anyone One is:
[ $(expr $mystring : ".*${search}.*") -ne 0 ] && echo 'yes' || echo 'no'
Answered 2023-09-20 20:16:44
expr
is one of those swiss-army-knife utilities that can usually do whatever it is you need to do, once you figure out how to do it, but once implemented, you can never remember why or how it's doing what it's doing, so you never touch it again, and hope that it never stops doing what it's doing. - anyone expr
, on rare occasion, when portability prevents using bash (eg., inconsistent behavior across older versions), tr (inconsistent everywhere) or sed (sometimes too slow). But from personal experience, whenever re-reading these expr
-isms, I have to go back to the man page. So, I would just comment that every usage of expr
be commented... - anyone expr
and test
were implemented to perform them. In this day and age, there are usually better tools, many of them built into any modern shell. I guess test
is still hanging in there, but nobody seems to be missing expr
. - anyone expr: syntax error: unexpected argument ‘.*.*’
bash: [: -ne: unary operator expected
- anyone This Stack Overflow answer was the only one to trap space and dash characters:
# For null cmd arguments checking
to_check=' -t'
space_n_dash_chars=' -'
[[ $to_check == *"$space_n_dash_chars"* ]] && echo found
Answered 2023-09-20 20:16:44
Since the POSIX/BusyBox question is closed without providing the right answer (IMHO), I'll post an answer here.
The shortest possible answer is:
[ ${_string_##*$_substring_*} ] || echo Substring found!
or
[ "${_string_##*$_substring_*}" ] || echo 'Substring found!'
Note that the double hash is obligatory with some shells (ash
). Above will evaluate [ stringvalue ]
when the substring is not found. It returns no error. When the substring is found the result is empty and it evaluates [ ]
. This will throw error code 1 since the string is completely substituted (due to *
).
The shortest more common syntax:
[ -z "${_string_##*$_substring_*}" ] && echo 'Substring found!'
or
[ -n "${_string_##*$_substring_*}" ] || echo 'Substring found!'
Another one:
[ "${_string_##$_substring_}" != "$_string_" ] && echo 'Substring found!'
or
[ "${_string_##$_substring_}" = "$_string_" ] || echo 'Substring found!'
Note the single equal sign!
Answered 2023-09-20 20:16:44
My .bash_profile file and how I used grep:
If the PATH environment variable includes my two bin
directories, don't append them,
# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
U=~/.local.bin:~/bin
if ! echo "$PATH" | grep -q "home"; then
export PATH=$PATH:${U}
fi
Answered 2023-09-20 20:16:44
grep -q -E 'pattern1|...|patternN'
. - anyone Extension of the question answered here How do you tell if a string contains another string in POSIX sh?:
This solution works with special characters:
# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
string="$1"
substring="$2"
if echo "$string" | $(type -p ggrep grep | head -1) -F -- "$substring" >/dev/null; then
return 0 # $substring is in $string
else
return 1 # $substring is not in $string
fi
}
contains "abcd" "e" || echo "abcd does not contain e"
contains "abcd" "ab" && echo "abcd contains ab"
contains "abcd" "bc" && echo "abcd contains bc"
contains "abcd" "cd" && echo "abcd contains cd"
contains "abcd" "abcd" && echo "abcd contains abcd"
contains "" "" && echo "empty string contains empty string"
contains "a" "" && echo "a contains empty string"
contains "" "a" || echo "empty string does not contain a"
contains "abcd efgh" "cd ef" && echo "abcd efgh contains cd ef"
contains "abcd efgh" " " && echo "abcd efgh contains a space"
contains "abcd [efg] hij" "[efg]" && echo "abcd [efg] hij contains [efg]"
contains "abcd [efg] hij" "[effg]" || echo "abcd [efg] hij does not contain [effg]"
contains "abcd *efg* hij" "*efg*" && echo "abcd *efg* hij contains *efg*"
contains "abcd *efg* hij" "d *efg* h" && echo "abcd *efg* hij contains d *efg* h"
contains "abcd *efg* hij" "*effg*" || echo "abcd *efg* hij does not contain *effg*"
Answered 2023-09-20 20:16:44
contains "-n" "n"
doesn't work here, because echo -n
will swallow the -n
as an option! A popular fix for that is to use printf "%s\n" "$string"
instead. - anyone The generic needle haystack example is following with variables
#!/bin/bash
needle="a_needle"
haystack="a_needle another_needle a_third_needle"
if [[ $haystack == *"$needle"* ]]; then
echo "needle found"
else
echo "needle NOT found"
fi
Answered 2023-09-20 20:16:44
grep -q
is useful for this purpose.
The same using awk
:
string="unix-bash 2389"
character="@"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'
Output:
Not Found
string="unix-bash 2389"
character="-"
printf '%s' "$string" | awk -vc="$character" '{ if (gsub(c, "")) { print "Found" } else { print "Not Found" } }'
Output:
Found
Original source: http://unstableme.blogspot.com/2008/06/bash-search-letter-in-string-awk.html
Answered 2023-09-20 20:16:44
echo
is unportable, you should be using printf '%s' "$string"
instead. I'm editing the answer because the user doesn't appear to exist anymore. - anyone I like sed.
substr="foo"
nonsub="$(echo "$string" | sed "s/$substr//")"
hassub=0 ; [ "$string" != "$nonsub" ] && hassub=1
Edit, Logic:
Use sed to remove instance of substring from string
If new string differs from old string, substring exists
Answered 2023-09-20 20:16:44
I found to need this functionality quite frequently, so I'm using a home-made shell function in my .bashrc
like this which allows me to reuse it as often as I need to, with an easy to remember name:
function stringinstring()
{
case "$2" in
*"$1"*)
return 0
;;
esac
return 1
}
To test if $string1
(say, abc) is contained in $string2
(say, 123abcABC) I just need to run stringinstring "$string1" "$string2"
and check for the return value, for example
stringinstring "$str1" "$str2" && echo YES || echo NO
Answered 2023-09-20 20:16:44
x
hack is only required for very old shells. - anyone strstr()
:-) - anyone case $string in (*foo*)
# Do stuff
esac
This is the same answer as https://stackoverflow.com/a/229585/11267590. But simple style and also POSIX Compliant.
Answered 2023-09-20 20:16:44
Exact word match:
string='My long string'
exactSearch='long'
if grep -E -q "\b${exactSearch}\b" <<<${string} >/dev/null 2>&1
then
echo "It's there"
fi
Answered 2023-09-20 20:16:44
I use this function (one dependency not included but obvious). It passes the tests shown below. If the function returns a value > 0 then the string was found. You could just as easily return 1 or 0 instead.
function str_instr {
# Return position of ```str``` within ```string```.
# >>> str_instr "str" "string"
# str: String to search for.
# string: String to search.
typeset str string x
# Behavior here is not the same in bash vs ksh unless we escape special characters.
str="$(str_escape_special_characters "${1}")"
string="${2}"
x="${string%%$str*}"
if [[ "${x}" != "${string}" ]]; then
echo "${#x} + 1" | bc -l
else
echo 0
fi
}
function test_str_instr {
str_instr "(" "'foo@host (dev,web)'" | assert_eq 11
str_instr ")" "'foo@host (dev,web)'" | assert_eq 19
str_instr "[" "'foo@host [dev,web]'" | assert_eq 11
str_instr "]" "'foo@host [dev,web]'" | assert_eq 19
str_instr "a" "abc" | assert_eq 1
str_instr "z" "abc" | assert_eq 0
str_instr "Eggs" "Green Eggs And Ham" | assert_eq 7
str_instr "a" "" | assert_eq 0
str_instr "" "" | assert_eq 0
str_instr " " "Green Eggs" | assert_eq 6
str_instr " " " Green " | assert_eq 1
}
Answered 2023-09-20 20:16:44
str_escape_special_characters
appears to have become str_escape
. see arcshell_str.sh
@ arclogicsoftware/arcshell - anyone You can use a logic && to be more compact
#!/bin/bash
# NO MATCH EXAMPLE
string="test"
[[ "$string" == *"foo"* ]] && {
echo "YES"
}
# MATCH EXAMPLE
string="tefoost"
[[ "$string" == *"foo"* ]] && {
echo "YES"
}
Answered 2023-09-20 20:16:44
Try oobash.
It is an OO-style string library for Bash 4. It has support for German umlauts. It is written in Bash.
Many functions are available:
base64Decode
base64Encode
capitalize
center
charAt
concat
contains
count
endsWith
equals
equalsIgnoreCase
reverse
hashCode
indexOf
isAlnum
isAlpha
isAscii
isDigit
isEmpty
isHexDigit
isLowerCase
isSpace
isPrintable
isUpperCase
isVisible
lastIndexOf
length
matches
replaceAll
replaceFirst
startsWith
substring
swapCase
toLowerCase
toString
toUpperCase
trim
zfill
Look at the contains example:
[Desktop]$ String a testXccc
[Desktop]$ a.contains tX
true
[Desktop]$ a.contains XtX
false
Answered 2023-09-20 20:16:44
msg="message"
function check {
echo $msg | egrep [abc] 1> /dev/null
if [ $? -ne 1 ];
then
echo "found"
else
echo "not found"
fi
}
check
This will find any occurance of a or b or c
Answered 2023-09-20 20:16:44
Here is the POSIX variant but with sed
:
string="My string"
pattern="string"
if [ "${string}" != "$(printf '%s' "${string}" | sed 's/'"${pattern}"'//g')" ]; then
echo "It's there!";
fi
Some explanation:
sed 's/'"${pattern}"'//g'
strips the pattern of the ${string}
.
So, it will look this way:
if [ "My string" != "My " ];
They are not equal and it's true
, so it means that the pattern was there.
If you use a different pattern like pattern="foo"
, the equation will be:
if [ "My string" != "My string" ];
Because sed
will not strip anything in this case and it will produce false
.
It still looks clumsy but this option will work in many shells like dash, zsh and not only bash.
Answered 2023-09-20 20:16:44
With jq:
string='My long string'
echo $string | jq -Rr 'select(contains("long"))|"It is there"'
The hardest thing in jq is to print the single quote:
echo $string | jq --arg quote "'" -Rr 'select(contains("long"))|"It\($quote)s there"'
Using jq just to check the condition:
if jq -Re 'select(contains("long"))|halt' <<< $string; then
echo "It's there!"
fi
Answered 2023-09-20 20:16:44