2911

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.

Andrzej Sydor
  • 1,180
  • 4
  • 9
  • 21
davidsheldon
  • 32,393
  • 4
  • 25
  • 27
  • 4
    Hi, if empty strings are false, why do you consider it clumsy? It was the only way that worked for me, despite the proposed solutions. – ericson.cepeda May 05 '15 at 06:14
  • 1
    You can use the `expr` command here – cifer Mar 02 '16 at 03:08
  • 4
    Here's one for posix shells: http://stackoverflow.com/questions/2829613/how-do-you-tell-if-a-string-contains-another-string-in-unix-shell-scripting – sehe Apr 08 '16 at 15:31

26 Answers26

4111

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

michael-slx
  • 371
  • 4
  • 7
Adam Bellaire
  • 99,441
  • 19
  • 144
  • 160
  • 163
    Also note that you can reverse the comparison by just switching to != in the test. Thanks for the answer! – Quinn Taylor Jul 30 '09 at 17:14
  • 2
    Hmm, with this exact code, I get `[[: not found`. Any idea what's wrong? I'm using GNU bash, version 4.1.5(1), on Ubuntu. – Jonik Nov 16 '10 at 11:20
  • 71
    @Jonik: You may be missing the shebang or have it as `#!/bin/sh`. Try `#!/bin/bash` instead. – Dennis Williamson Dec 17 '10 at 05:18
  • +1 for your answer. Thanks. I was looking at some code which uses this syntax. Though I guessed at the intention now I've got confirmation. Tried the bash scripting guide and google for quite some time but couldn't find what I wanted until I saw your answer. Can you give a link to such uses of strings and wildcard matching? Thanks!! – GuruM Sep 07 '11 at 10:37
  • @DennisWilliamson I am getting the same problem as Jonik, but i have the correct `#!/bin/bash` at the top. What else could be causing the problem? – prolink007 Oct 19 '12 at 16:32
  • 4
    I figured out the issue. If you are calling the script from another script make sure you are using `bash nameofscript.sh` instead of `sh nameofscript.sh`. – prolink007 Oct 19 '12 at 16:48
  • No success. What is wrong with this statement? `if [["Oh My God" == *My*]]; then echo Yes! ; else echo no.. ; fi` It returns no. This is in bash/Ubuntu 12.10 – Redsandro Dec 05 '12 at 20:06
  • 16
    Leave a space between the brackets and the contents. – Paul Price Jan 22 '13 at 16:43
  • 20
    You don't need to quote variables inside [[ ]]. This will work just as well: [[ $string == *$needle* ]] && echo found – Orwellophile Aug 09 '13 at 05:07
  • +1 to the original answer. But the edit is at best subjective and at worst inaccurate (you don't need to quote `$string` here, though doing so may be a good practice), and should obviously be rolled back. – Reinstate Monica Please Jan 09 '14 at 00:28
  • 1
    This doesn't seem to work when I have a colon ":" in the string if [[ $string == *My: test* ]] – Dss Jan 13 '14 at 16:57
  • how to check whether string contains * ? – user3153014 Jun 11 '14 at 05:45
  • 19
    @Orwellophile **Careful!** You can only omit the double quotes in the first argument to `[[`. See http://ideone.com/ZSy1gZ – nyuszika7h Jun 11 '14 at 17:53
  • @nyuszika7h - what a mess :) i would have used a regular expression, which you can't quote in bash4 anyway. – Orwellophile Jun 24 '14 at 14:24
  • 3
    By the way, make sure the asterisks are not in quotes: `if [[ " $args" == *" -l"* ]]` – redolent Jul 24 '14 at 18:01
  • 2
    It took me a long while to realise that with `#!/bin/bash`, spaces are needed between each element of a statement, e.g `if [[ $input_var == "test" ]]` – Shailen Aug 03 '14 at 14:56
  • @Dss mentioned that it was the space, just thought I'd add you need to escape it: `if [[ $string == *My\ String* ]]` – Tim S. Nov 19 '14 at 16:16
  • I had used above with only difference i had the string & substring switched places in the comparison and did not work for me, Had me stumped for a while. Anyone know why? – harish Feb 18 '15 at 16:46
  • 2
    Warning: It's a so called *bashism*! As @DennisWilliamson pointed out, this work only under [tag:bash], but not with regular [tag:shell]! ( I've wrote a [compatible function](http://stackoverflow.com/a/20460402/1765658) which could work under poor shell like busybox ) – F. Hauri May 31 '15 at 14:36
  • 1
    @F.Hauri, this is not a bashism. It also works with POSIX `sh` and `ksh`. – maxschlepzig Apr 30 '17 at 15:11
  • @VasyaNovikov, 'Word splitting and pathname expansion are not performed on the words between the `[[` and `]]`' (bash(1)) – maxschlepzig Apr 30 '17 at 15:21
  • @maxschlepzig right. I didn't know that when I was writing the comment.. Will delete the old comment and this one in a couple of hours.. – VasiliNovikov Apr 30 '17 at 20:08
  • 2
    @maxschlepzig You're wrong: It's *bashisms*! Care that your `/bin/sh` is not a link to `bash`! For myself, I just tried `dash` now: `dash: 2: [[: not found` – F. Hauri May 01 '17 at 05:33
  • 1
    @F.Hauri, [POSIX 2008](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_04) lists `[[` as unspecified reserved word. But still: KSH 88 and the `/usr/xpg4/bin/sh` under Solaris 10 ('a standards compliant shell') both understand `[[`. Thus, it is NOT a bashism. – maxschlepzig May 01 '17 at 09:58
  • I've noticed, if you run this `if [[ $string == *"linux"* ]]; then` command in a directory that contains file 'README.linux', then `*"linux"*` will expand to `README.linux`, that is `*` will glob - and so the matching will fail. – sdaau Oct 26 '17 at 19:18
  • 3
    Attention! `if [[ *"My long"* == $string ]];` does not work whereas `if [[ $string == *"My long"* ]];` works. Is there anyone to explain this? – Fredrick Gauss Nov 07 '17 at 06:51
  • 1
    @sdaau Not reproduced under Bash with my test. No globbing happened. The bash info page actually clearly says: "Word splitting and filename expansion are **not** performed on the words between the '[[' and ']]'" – Yongwei Wu Jan 19 '18 at 07:46
  • 1
    @FredrickGauss The Bash info page says: "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 in *note Pattern Matching::. If the shell option 'nocasematch' (see the description of 'shopt' in *note The Shopt Builtin::) 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 pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string." – Yongwei Wu Jan 19 '18 at 07:49
  • why not use == instead of =? – Alexander Mills May 02 '18 at 06:23
  • It will throw an syntax error if the `$string` variable is empty. Can you fix it? – user Nov 04 '19 at 03:12
  • error for me as I have " quotes in my strings that I need to test for. – JPM Sep 21 '20 at 22:54
  • @nyuszika7h (back after googling to remember something) -- sorry, i don't think i really appreciated your comment about quoting the second argument. i'm not really sure how i feel. on one hand it's useful to allow a user to input wildcards and use them (unquoted $b) or **not use them** (quoted $b), and all with the safety of globbing. but i'm not quite sure the extra power makes up for the inevitable bugs from misuse. – Orwellophile Nov 04 '20 at 06:58
  • @nyuszika7h _"Careful! You can only omit the double quotes in the first argument to [[."_ No dear, **this is wrong**. The second argument can be unquoted too. The exampe in your link is about **globbing** and says: 1. It only happens in the second argument. 2. And of course, only when unquoted. – Small Boy May 20 '21 at 21:08
737

If you prefer the regex approach:

string='My string';

if [[ $string =~ "My" ]]; then
   echo "It's there!"
fi
Acumenus
  • 41,481
  • 14
  • 116
  • 107
Matt Tardiff
  • 7,601
  • 1
  • 14
  • 9
  • 2
    Had to replace an egrep regex in a bash script, this worked perfectly! – blast_hardcheese Feb 14 '12 at 05:10
  • 117
    The `=~` operator already searches the whole string for a match; the `.*`'s here are extraneous. Also, quotes are generally preferable to backslashes: `[[ $string =~ "My s" ]]` – bukzor Jun 05 '13 at 18:15
  • 18
    @bukzor Quotes stopped working here as of Bash 3.2+: http://tiswww.case.edu/php/chet/bash/FAQ `E14)`. It's probably best to assign to a variable (using quotes), then compare. Like this: `re="My s"; if [[ $string =~ $re ]]` – seanf May 12 '15 at 00:55
  • 43
    Test if it does **NOT** contain a string: `if [[ ! "abc" =~ "d" ]]` is true. – KrisWebDev Jan 24 '16 at 14:57
  • 1
    Note that the order in which the test occurs matters. The output of the following line of code will be forward 2. `number=2; if [[ "1, 2, 4, 5" = *${number}* ]]; then echo forward $number; fi; if [[ *${number}* = "1, 2, 4, 5" ]]; then echo backwards $number; fi;` – Douwe van der Leest Aug 01 '18 at 11:40
  • @bukzor thanks for clearly pointing out the meaning of the `=~` operator that checks for a match. Coming from Matlab, where `~=` is the `not equal` operator, this confused me a lot at the beginning. – Wolfson Apr 27 '20 at 08:49
  • Timing `time for i in {1..100000}; do [[ "abcfewgwbzsgreagreagrw" == *"bzsgre"* ]]; done` and `time for i in {1..100000}; do [[ "abcfewgwbzsgreagreagrw" =~ "bzsgre" ]]; done`. My computer gave that `=~` takes about twice time to `==`. – hychou May 07 '20 at 08:20
  • what if I need it to be case insensitive? – Hugo Licon Jul 20 '20 at 23:33
  • @hychou anybody who uses regular expression for fun and profit, will tell that **only** taking twice as long as doing it the non-regex way = a total bargain! – Orwellophile Nov 04 '20 at 06:51
  • @Orwellophile regex can do much more powerful things. But in this specific question (whether str1 contains str2), regex is overkill and costs more time. Therefore IMO, not a bargain this case. – hychou Nov 04 '20 at 07:07
407

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
Marcus Griep
  • 7,288
  • 1
  • 20
  • 23
  • 90
    This is probably the best solution since it is portable to posix shells. (a.k.a. no bashisms) – technosaurus Jan 04 '14 at 17:02
  • 32
    @technosaurus I find it rather odd to criticize "bashism" in a question that has only bash tag :) – P.P Dec 17 '15 at 23:27
  • 49
    @P.P. It's not so much a criticism as the preference of a more universal solution over a more limited one. Please consider that, years later, people (like me) will stop by to look for this answer and may be pleased to find one that's useful in a wider scope than the original question. As they say in the Open Source world: "choice is good!" – Carl Smotricz Jun 03 '16 at 07:56
  • 2
    @technosaurus, FWIW `[[ $string == *foo* ]]` also works in some POSIX compliant sh versions (e.g. `/usr/xpg4/bin/sh` on Solaris 10) and ksh (>= 88) – maxschlepzig May 01 '17 at 10:00
  • @maxschlepzig ... not if there are characters in $IFS such as space, newline, tab or user defined separator... case statement works though without wierd mixed quotation or having to push and pop IFS – technosaurus May 01 '17 at 22:28
  • @technosaurus, you mean because of word-splitting? With `[[` there is no word-splitting (cf. e.g. `ksh(1)`). This example `string="hello foo world"; [[ $string == *foo* ]] && echo true` works for me as expected. Even with ksh 88. Yes, in a fresh shell that has the default `IFS` value (i.e. space+newline+tab). – maxschlepzig May 01 '17 at 22:39
  • @maxschlepzig Then they are not POSIX compliant. Ksh is obviously Korn shell, not POSIX `sh`; and Solaris 10 XPG `sh` is *definitely* not POSIX. – tripleee Apr 12 '18 at 06:40
  • @tripleee, nobody claimed that ksh is POSIX sh. And you are wrong about XPG4: 'superset of POSIX.1-1990, POSIX.2-1992, and POSIX.2a-1992 containing extensions to POSIX standards from XPG3' [XPG4(5)](https://docs.oracle.com/cd/E19455-01/806-0634/6j9vo5anl/index.html). You can also ask yourself what `sh` version Sun used to get certified as POSIX compliant. – maxschlepzig Apr 12 '18 at 18:58
  • Then by the same logic Bash is POSIX. It is simply misleading to say that this is a feature in some POSIX shells; it is definitely a non-POSIX feature and so whether those shells are POSIX or not is kind of beside the point. Like you say, the shell contains extensions beyond POSIX. – tripleee Apr 13 '18 at 03:57
  • 3
    Busybox ash does not handle asterisks in `[[` ... the case-switch did work! – Ray Foss Mar 25 '19 at 14:50
281

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="$@"
    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
F. Hauri
  • 51,421
  • 13
  • 88
  • 109
  • 1
    This would be even better, if you can figure out some way to put that to a function. – Eero Aaltonen Dec 10 '13 at 08:35
  • 2
    @EeroAaltonen How do you find my (new added) function? – F. Hauri May 06 '14 at 18:23
  • 2
    I know! find . -name "*" | xargs grep "myfunc" 2> /dev/null – eggmatters Jul 15 '14 at 20:20
  • @eggmatters Question stand for *string* that contain *string* (not file containing string). – F. Hauri Jul 15 '14 at 22:14
  • 1
    @F.Hauri Sorry, was a joke to your comment to EuroAaltonen The find command has absolutely nothing to do with the question posted on this thread. – eggmatters Jul 16 '14 at 23:15
  • $F.Hauri It is what I was thinking and hoping for. The only change I would make is to the name. I propose "stringContain", since at least integers are supported. – Eero Aaltonen Sep 25 '14 at 11:15
  • 7
    This is wonderful because it's so compatible. One bug, though: It does not work if the haystack string is empty. The correct version would be `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). – Sjlver Oct 24 '14 at 12:14
  • 3
    +1. Very good! For me I changed order stringContain() { [ -z "${1##*$2*}" ] && [ -z "$2" -o -n "$1" ]; }; "Search where" "Find what". Work in busybox. Accepted answer above don't work in busybox. – user3439968 Nov 14 '14 at 19:11
  • Similarly, if [ -z ${string%%*$subreqstr*} ]; then echo there; else echo not-there; fi #will work too. – AKS Oct 21 '16 at 22:13
  • @ArunSangal Yes, this is technicaly same way of thinking, but there could be some difference in practice... I would let you try to bench this over some thousand of tests to see if [tag:bash] could be worst in one way, than the other... ;-) – F. Hauri Aug 25 '17 at 20:53
  • **Special case:** The string to search for is **the first** word in each lines (*`processor`* in *`/proc/cpuinfo`*) see [this answer to *How to obtain the number of CPUs/cores in Linux from the command line?*](https://stackoverflow.com/a/48749737/1765658)! – F. Hauri Feb 12 '18 at 15:30
  • 1
    The `-a` and `-o` extensions to `[` are not exactly portable as POSIX specifies behavior to only four operators. `[ -n "$2" ] && [ -z "${2##*$1*}" ] || [ -z "$1" ]` is portable and follows the behavior of implementations from other languages, although I think switching the arguments would be clearer yet (haystack before needle). – Adrian Günter Apr 10 '18 at 16:39
  • @AdrianGünter As `[` is a *command* and `&&` a separation between two commands, `[ -z "$1" -o -n "$2" ]` only have to be considered, there are no more than four operators (in fact two operators as `-n` and `-z` use implicit operators). – F. Hauri Apr 11 '18 at 14:50
  • 1
    @F.Hauri I was lazily referring to both operators (`-a`, `-o`, `-n`, `-z`, ...) and primaries/operands (`$1`, `$2`, ...) as operators (or better yet, *arguments*) to `[` (AKA `test`). Point being, `[ -z "$1" -o -n "$2" ]` is passing five arguments to `[` which is unspecified and therefore unreliable and unportable. See: https://unix.stackexchange.com/a/57843/83127 and http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html `>4 arguments: The results are unspecified.` – Adrian Günter Apr 12 '18 at 00:55
  • @AdrianGünter. Hem.. care in translating boolean: `a & ( b | c )` is not same than `c & b | a` ! – F. Hauri Apr 12 '18 at 06:20
  • Even Busybox has `case`, I believe. – tripleee Apr 12 '18 at 06:49
  • 1
    @F.Hauri I changed the order here to further optimize, but `[ -z "$1" ] || [ -n "$2" ] && [ -z "${2##*$1*}" ]` and `[ -z "${2##*$1*}" ] && { [ -z "$1" ] || [ -n "$2" ] }` are functionally equivalent, except that your version is less efficient. Consider: if `$1`, the needle/search substring, is empty (`""`) then you do not need to test for `[ -z "${2##*$1*}" ]` at all, as *every* string (including an empty one!) contains `""`. If `$1` is an empty string `strContain` should always return success (`0`). If you test our versions side-by-side you should find them to be functionally equivalent. – Adrian Günter Apr 12 '18 at 17:48
  • Is it really a poorer shell? It's maybe more limited but it's statically linked isn't it? That's great for system recovery (of some mistakes or errors) and other such things. Maybe it's not ever necessary to have this functionality in such situations but you never know. This really complements the other answers! – Pryftan Dec 21 '18 at 14:29
  • 1
    That's nice to say if something is `bash`, `ksh` or vanilla `shell` compatible, thx – Sandburg Jan 11 '19 at 08:33
  • It's easy to change this to check if a string **doesn't** contain a substring: all you have to do is use `-n` instead of `-z` or omit any options entirely. – S.S. Anne May 23 '20 at 23:38
  • @S.S.Anne `if ! stringContain "$1" "$string";then ...` – F. Hauri May 25 '20 at 06:19
  • No, I'm saying it's easy to change this to check if a string doesn't contain another (and I already know how to). Double-negation isn't not bad and not not confusing. – S.S. Anne May 25 '20 at 16:27
168

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

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Mark Baker
  • 5,199
  • 2
  • 24
  • 25
  • 7
    they are called [here strings (3.6.7)](http://www.gnu.org/s/bash/manual/bash.html) I believe it is bashism – alex.pilon Oct 20 '11 at 17:03
  • 12
    one can also use [Process Substitution](http://www.gnu.org/s/bash/manual/bash.html#Process-Substitution) `if grep -q foo – larsr Dec 19 '11 at 12:45
  • Note that `echo` is unportable, if you're passing a variable, use `printf '%s' "$string` instead. – nyuszika7h Jun 11 '14 at 18:05
  • @nyuszika7h `echo` alone is pretty portable. The flags are not. If you find yourself thinking about `-e` or `-n` use `printf` – Bruno Bronosky Mar 28 '15 at 07:10
  • 6
    The cost of this is very expensive: doing `grep -q foo << – F. Hauri Apr 20 '15 at 08:40
  • 1
    @BrunoBronosky `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'` – tripleee Apr 12 '18 at 06:45
  • @TechZilla Remember, we are talking about a Bash script. No one would write a Bash script for performance critical code. – ahoffer Jun 06 '18 at 05:07
  • I am test on this – Wang Shaowei Apr 14 '20 at 01:41
97

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.

Steven Penny
  • 82,115
  • 47
  • 308
  • 348
ephemient
  • 180,829
  • 34
  • 259
  • 378
  • 5
    ephemient's solution above: > ` if [ "$string" != "${string/foo/}" ]; then echo "It's there!" fi` is useful when using BusyBox's shell *ash*. The accepted solution does not work with BusyBox because some bash's regular expressions are not implemented. – TPoschel Oct 08 '10 at 12:41
  • 2
    the inequality of difference. Pretty weird thought! I love it – nitinr708 Aug 01 '17 at 08:58
  • 1
    unless your string is 'foo' though – venimus Jul 12 '19 at 17:31
  • I wrote this same solution myself (because my interpreter wouldn't take the top answers) then went looking for a better one, but found this! – BuvinJ Oct 15 '19 at 20:25
  • doesn't seem to work for me: running Ubuntu Mate 20.04, `$XDG_CURRENT_DESKTOP` contains `MATE`, and running: ```if [ "$XDG_CURRENT_DESKTOP" != "${string/GNOME/}" ]; then echo MATCHES GNOME fi ``` prints MATCHES GNOME... it thinks "MATE" matches "GNOME" ... dafuq? – hanshenrik Sep 01 '20 at 19:30
  • @hanshenrik You're comparing `$XDG_CURRENT_DESKTOP` to `$string`. The expression you want is `if [ "$XDG_CURRENT_DESKTOP" != "${XDG_CURRENT_DESKTOP/GNOME/}" ]; then echo MATCHES GNOME; fi` – Todd Lewis Mar 26 '21 at 14:28
69

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Paul Hedderly
  • 3,437
  • 2
  • 15
  • 12
  • 7
    Neat benchmark. Convinced me to use `[[ $b == *$a* ]]`. – Mad Physicist Jul 13 '16 at 16:28
  • 2
    If I'm reading this correctly, `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. – tripleee Apr 12 '18 at 06:59
  • 1
    https://ideone.com/5roEVt has my experiment with some additional bugs fixed and tests for a different scenario (where the string is actually not present in the longer string). Results are largely similar; `[[ $b == *$a* ]]` is quick and `case` is almost as quick (and pleasantly POSIX-compatible). – tripleee Apr 12 '18 at 08:41
  • The conditional expression `[[ $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` – r a Feb 19 '21 at 13:49
30

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.

kevinarpe
  • 17,685
  • 21
  • 107
  • 133
  • 5
    ...but the OP doesn't say which _version_ of bash; e.g., older bash's (such as solaris frequently has) may not include these newer bash features. (I've run into this exact problem (bash pattern matching not implemented) on solaris w/ bash 2.0) – michael Aug 10 '13 at 05:43
  • 2
    `echo` is unportable, you should be using `printf '%s' "$haystack` instead. – nyuszika7h Jun 11 '14 at 18:03
  • @nyuszika7h: I was not away `echo` is unportable. Will `/bin/echo` work? – kevinarpe Aug 16 '14 at 11:15
  • 2
    Nope, just avoid `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. – nyuszika7h Aug 16 '14 at 11:18
  • @nyuszika7h: Do you know if `printf -- ` is portable or only `printf `? Out of habit, I used `printf -- ` to be safe. – kevinarpe Mar 17 '15 at 04:52
  • 1
    @kevinarpe I'm not sure, `--` is not listed in the [POSIX spec for `printf`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html), but you should use `printf '%s' "$anything"` anyway, to avoid issues if `$anything` contains a `%` character. – nyuszika7h Mar 17 '15 at 13:45
  • @nyuszika7h: Regarding support for `--`, I found this POSIX spec for `getopt()`: http://pubs.opengroup.org/onlinepubs/9699919799/functions/getopt.html. Do you think it is safe to assume that if `printf` is POSIX defined / compatible, then `--` will also be supported? – kevinarpe Jun 17 '16 at 07:14
  • 1
    @kevinarpe Based on that, it probably is. – nyuszika7h Jun 17 '16 at 10:49
25

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

Enjoy.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Mike Q
  • 5,006
  • 2
  • 41
  • 53
  • 1
    Aaaah - I only understood it after I found out that the two commas in `${str,,}` convert `$str` to lower case. Great solutions / great list! – hey Jan 17 '21 at 22:08
21

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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Samuel
  • 5,919
  • 6
  • 35
  • 39
19

How about this:

text="   <tag>bmnmn</tag>  "
if [[ "$text" =~ "<tag>" ]]; then
   echo "matched"
else
   echo "not matched"
fi
bn.
  • 7,031
  • 7
  • 37
  • 53
  • 2
    =~ is for regexp matching, hence too powerful for the OP's purpose. –  Feb 09 '09 at 06:37
12

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Yordan Georgiev
  • 4,006
  • 1
  • 43
  • 47
11
[[ $string == *foo* ]] && echo "It's there" || echo "Couldn't find"
Jahid
  • 18,228
  • 8
  • 79
  • 95
  • I will add that the `echo "Couldn't find` statement at the end is a nice trick to return 0 exit statuses for these matching commands. – nicodjimenez Oct 16 '17 at 21:11
  • @nicodjimenez you can not target exit status any more with this solution. Exit status is swallowed up by the status messages ... – Jahid Oct 17 '17 at 05:56
  • 1
    That's exactly what I meant... If you don't have `|| 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 – nicodjimenez Oct 17 '17 at 10:02
11

One is:

[ $(expr $mystring : ".*${search}.*") -ne 0 ] && echo 'yes' ||  echo 'no'
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
chemila
  • 3,697
  • 4
  • 19
  • 18
  • 2
    `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. – michael Aug 10 '13 at 05:50
  • @AloisMahdal I never down-voted, I'm just postulating on why downvotes were given. A cautionary comment. I do use `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... – michael Mar 04 '14 at 19:13
  • 1
    There was a time when all you had was the original Bourne shell. It lacked some commonly required features, so tools like `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`. – tripleee Feb 10 '16 at 18:12
  • Upvoting since I needed something that worked in Bourne shell, and everything else appears to be bash-specific. – Svet May 03 '21 at 23:11
6

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
  • 1
    Is this an answer? – codeforester Jan 14 '17 at 06:01
  • upvote. why bother to learn how Bash does it when grep, far more powerful, is more than likely going to be available. also extend it a bit further by matching against a list of patterns: `grep -q -E 'pattern1|...|patternN'`. – Mohamed Bana Feb 07 '17 at 13:37
5

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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Kurt Pfeifle
  • 78,224
  • 20
  • 220
  • 319
5

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

ride
  • 129
  • 2
  • 2
  • 3
    Please add some explanation. Imparting the underlying logic is more important than just giving the code, because it helps the OP and other readers fix this and similar issues themselves – Zulan Mar 05 '16 at 14:04
5

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

nyuszika7h
  • 12,020
  • 5
  • 40
  • 49
5

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*"
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Alex Skrypnyk
  • 1,114
  • 12
  • 12
  • 2
    The test `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. – joeytwiddle Dec 12 '19 at 10:34
5

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!

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
FifthAxiom
  • 101
  • 1
  • 5
3

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, and -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.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
andreas
  • 63
  • 1
3

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
Eduardo Cuomo
  • 13,985
  • 3
  • 93
  • 80
3
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.

2

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
}
Ethan Post
  • 2,882
  • 2
  • 24
  • 27
  • The suspense! What *is* the dependency? – Peter Mortensen Jan 01 '20 at 14:22
  • The "str_escape_special_characters" function. It is in GitHub arcshell_str.sh file. arcshell.io will get you there. – Ethan Post Jan 02 '20 at 05:47
  • `str_escape_special_characters` appears to have become `str_escape`. see `arcshell_str.sh` @ [arclogicsoftware/arcshell](https://github.com/arclogicsoftware/arcshell/blob/master/sh/core/arcshell_str.sh) – pkfm Oct 03 '20 at 17:47
1

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
Pipo
  • 3,387
  • 31
  • 36
0
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

BobMonk
  • 170
  • 9