davidsheldon
davidsheldon

Reputation: 40065

How to check if a string contains a substring in Bash

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.

Upvotes: 3623

Views: 3305731

Answers (30)

andreas
andreas

Reputation: 45

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

oobash is available at Sourceforge.net.

Upvotes: -6

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70977

Full rewrite 2023-07-03!!

String contain: POSIX compatibility, bash case independent, hints and remarks.

Introduction

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!

Brief essential

case $string in
    *$substring* )
         do something with "$substring"
         ;;
esac 

As a function:

stringContain() { case $2 in *$1* ) return 0;; *) return 1;; esac ;}

Usage sample

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: ''           ''

Alternative using parameter expansion

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 than parameter expansion method regardless shell implementation used.

Semantically:

  • case method: in case string match anything (could be nothing), followed by substring, followed by anything. is a single test.
  • parameter expansion: If substring is empty or string where anything followed by substring followed by anything is replaced by nothing is nothing and string do contain something is a complex multiple test after string transformation

From this point of view, this seem easy to understand that case method is more efficient!

Case independent

Under and some other , 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

Upvotes: 363

t7e
t7e

Reputation: 509

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.

Upvotes: 0

Mike Q
Mike Q

Reputation: 7337

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.

Upvotes: 82

JanB
JanB

Reputation: 85

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

Upvotes: -1

Francesco Gasparetto
Francesco Gasparetto

Reputation: 1963

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"
}

Upvotes: 3

Piotr Henryk Dabrowski
Piotr Henryk Dabrowski

Reputation: 911

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

Upvotes: 21

Matt Tardiff
Matt Tardiff

Reputation: 10629

If you prefer the regex approach:

string='My string';

if [[ $string =~ "My" ]]; then
   echo "It's there!"
fi

Upvotes: 1039

Adam Bellaire
Adam Bellaire

Reputation: 110509

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

Upvotes: 4967

Koichi Nakashima
Koichi Nakashima

Reputation: 959

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.

Upvotes: 7

Pipo
Pipo

Reputation: 5093

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

Upvotes: 7

BobMonk
BobMonk

Reputation: 196

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

Upvotes: -1

FifthAxiom
FifthAxiom

Reputation: 300

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!

Upvotes: 7

Alex Skrypnyk
Alex Skrypnyk

Reputation: 1467

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*"

Upvotes: 6

Leslie Satenstein
Leslie Satenstein

Reputation: 416

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

Upvotes: 5

Samuel
Samuel

Reputation: 8945

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.

Upvotes: 28

Paul Hedderly
Paul Hedderly

Reputation: 3941

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.

Upvotes: 85

Yordan Georgiev
Yordan Georgiev

Reputation: 5440

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

Upvotes: 11

Kurt Pfeifle
Kurt Pfeifle

Reputation: 90295

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

Upvotes: 5

Mark Baker
Mark Baker

Reputation: 5705

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).

Upvotes: 192

Ethan Post
Ethan Post

Reputation: 3050

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
}

Upvotes: 2

Eduardo Cuomo
Eduardo Cuomo

Reputation: 19006

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

Upvotes: 4

ride
ride

Reputation: 129

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

Upvotes: 5

chemila
chemila

Reputation: 4351

One is:

[ $(expr $mystring : ".*${search}.*") -ne 0 ] && echo 'yes' ||  echo 'no'

Upvotes: 11

kevinarpe
kevinarpe

Reputation: 21319

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.

Upvotes: 31

Jahid
Jahid

Reputation: 22428

[[ $string == *foo* ]] && echo "It's there" || echo "Couldn't find"

Upvotes: 12

Jadu Saikia
Jadu Saikia

Reputation:

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

Upvotes: 5

Stefan
Stefan

Reputation:

How about this:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi

Upvotes: 20

ephemient
ephemient

Reputation: 204956

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.

Upvotes: 107

Marcus Griep
Marcus Griep

Reputation: 8424

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

Upvotes: 500

Related Questions