174

I want to write a Unix shell script that will do various logic if there is a string inside of another string. For example, if I am in a certain folder, branch off. Could someone please tell me how to accomplish this? If possible I would like to make this not shell specific (i.e. not bash only) but if there's no other way I can make do with that.

#!/usr/bin/env sh

if [ "$PWD" contains "String1" ]
then
    echo "String1 present"
elif [ "$PWD" contains "String2" ]
then
    echo "String2 present"
else
    echo "Else"
fi
Reid
  • 1,999
  • 3
  • 17
  • 25
Matt
  • 2,503
  • 4
  • 31
  • 46
  • 3
    I realize this is old, but here are a few things to note for future visitors: (1) It's usually good practice to reserve SNAKE_CASE variable names for environment and shell internal variables. (2) Setting `CURRENT_DIR` is redundant; you can just use `$PWD`. – alexia Nov 06 '14 at 22:03

12 Answers12

215

Here's yet another solution. This uses POSIX substring parameter expansion, so it works in Bash, Dash, KornShell (ksh), Z shell (zsh), etc. It also supports special characters in strings.

test "${string#*"$word"}" != "$string" && echo "$word found in $string"

A functionalized version with some tests:

# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    if [ "${string#*"$substring"}" != "$string" ]; then
        return 0    # $substring is in $string
    else
        return 1    # $substring is not in $string
    fi
}

testcontains() {
    testnum="$1"
    expected="$2"
    string="$3"
    substring="$4"
    contains "$string" "$substring"
    result=$?
    if [ $result -eq $expected ]; then
        echo "test $testnum passed"
    else
        echo "test $testnum FAILED: string=<$string> substring=<$substring> result=<$result> expected=<$expected>"
    fi
}

testcontains  1 1 'abcd' 'e'
testcontains  2 0 'abcd' 'ab'
testcontains  3 0 'abcd' 'bc'
testcontains  4 0 'abcd' 'cd'
testcontains  5 0 'abcd' 'abcd'
testcontains  6 1 '' 'a'
testcontains  7 0 'abcd efgh' 'cd ef'
testcontains  8 0 'abcd efgh' ' '
testcontains  9 1 'abcdefgh' ' '
testcontains 10 0 'abcd [efg] hij' '[efg]'
testcontains 11 1 'abcd [efg] hij' '[effg]'
testcontains 12 0 'abcd *efg* hij' '*efg*'
testcontains 13 0 'abcd *efg* hij' 'd *efg* h'
testcontains 14 1 'abcd *efg* hij' '*effg*'
testcontains 15 1 'abcd *efg* hij' '\effg\'
testcontains 16 0 'a\b' '\'
testcontains 17 0 '\' '\'
testcontains 18 1 '[' '\'
testcontains 19 1 '\' '['
testcontains 20 0 '-n' 'n'
testcontains 21 1 'n' '-n'
testcontains 22 0 '*\`[]' '\`'
fjarlq
  • 2,419
  • 1
  • 16
  • 10
  • 3
    This doesn't work for me if the substring contains backslashes. As usual, `substring="$( printf '%q' "$2" )"` saves the day. – Egor Tensin Aug 10 '16 at 11:22
  • this matches wrong substrinsg too. string = "aplha beta betaone" substring="beta" . it matches both betaone an dbeta, whihc i think is wrong. – rajeev Aug 30 '17 at 21:04
  • 1
    What about [using wildcards](https://stackoverflow.com/a/229606/4970442)? `[[ $haystack == *"My needle"* ]]` – Pablo Bianchi Oct 05 '17 at 21:02
  • 12
    What's wrong is that double-brackets aren't POSIX, which was the premise of the question, @Pablo. – Rob Kennedy Sep 04 '18 at 22:38
  • 3
    This does not work with special characters like `[]`. See my answer https://stackoverflow.com/a/54490453/712666. – Alex Skrypnyk Feb 02 '19 at 06:00
  • 1
    We have to double quote the matching `"$word"`. https://www.shellcheck.net/wiki/SC2295 – midnite Mar 29 '23 at 16:52
  • 1
    Thanks, @midnite. ShellCheck is cool. I've edited this post so that it supports special characters now. Also thanks to Alex Skrypnyk and Egor Tensin for pointing out the same bug. – fjarlq Mar 31 '23 at 06:09
114

Pure POSIX shell:

#!/bin/sh
CURRENT_DIR=`pwd`

case "$CURRENT_DIR" in
  *String1*) echo "String1 present" ;;
  *String2*) echo "String2 present" ;;
  *)         echo "else" ;;
esac

Extended shells like ksh or bash have fancy matching mechanisms, but the old-style case is surprisingly powerful.

Norman Ramsey
  • 198,648
  • 61
  • 360
  • 533
50
#!/usr/bin/env sh

# Searches a subset string in a string:
# 1st arg:reference string
# 2nd arg:subset string to be matched

if echo "$1" | grep -q "$2"
then
    echo "$2 is in $1"
else 
    echo "$2 is not in $1"
fi
Daniele Orlando
  • 2,692
  • 3
  • 23
  • 26
a.saurabh
  • 1,163
  • 10
  • 15
38

Sadly, I am not aware of a way to do this in sh. However, using bash (starting in version 3.0.0, which is probably what you have), you can use the =~ operator like this:

#!/bin/bash
CURRENT_DIR=`pwd`

if [[ "$CURRENT_DIR" =~ "String1" ]]
then
 echo "String1 present"
elif [[ "$CURRENT_DIR" =~ "String2" ]]
then
 echo "String2 present"
else
 echo "Else"
fi

As an added bonus (and/or a warning, if your strings have any funny characters in them), =~ accepts regexes as the right operand if you leave out the quotes.

Clay
  • 4,700
  • 3
  • 33
  • 49
John Hyland
  • 6,855
  • 28
  • 32
  • 3
    Don't quote the regex, or it will *not* work in general. For example, try `[[ test =~ "test.*" ]]` vs. `[[ test =~ test.* ]]`. – l0b0 Jan 11 '12 at 08:01
  • 1
    Well, it will work just fine if you're testing for a substring, as in the original question, but it won't treat the right operand as a regex. I'll update my answer to make that more clear. – John Hyland Jun 25 '13 at 00:21
  • 8
    This is Bash, not POSIX sh as the question asks. – Reid Sep 27 '18 at 20:43
18
case $(pwd) in
  *path) echo "ends with path";;
  path*) echo "starts with path";;
  *path*) echo "contains path";;
  *) echo "this is the default";;
esac
helpermethod
  • 59,493
  • 71
  • 188
  • 276
12

Here is a link to various solutions of your issue.

This is my favorite as it makes the most human readable sense:

The Star Wildcard Method

if [[ "$string" == *"$substring"* ]]; then
    return 1
fi
return 0
ma77c
  • 1,052
  • 14
  • 31
6

There's Bash regular expressions. Or there's 'expr':

 if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
bmargulies
  • 97,814
  • 39
  • 186
  • 310
3

If you want a ksh only method that is as fast as "test", you can do something like:

contains() # haystack needle
{
    haystack=${1/$2/}
    if [ ${#haystack} -ne ${#1} ] ; then
        return 1
    fi
    return 0
}

It works by deleting the needle in the haystack and then comparing the string length of old and new haystacks.

JoeOfTex
  • 97
  • 1
  • 10
  • 1
    Wouldn't it be possible to return the result of `test`? As in `return [ ${#haystack} -eq ${#1} ]`? – Alexis Wilke Aug 22 '16 at 18:05
  • Yes, that is correct. I will leave it like this as its easier to understand for laymen. If you use in your code, use @AlexisWilke method. – JoeOfTex Jun 19 '19 at 21:29
2

See the manpage for the 'test' program. If you're just testing for the existence of a directory you would normally do something like so:

if test -d "String1"; then
  echo "String1 present"
end

If you're actually trying to match a string you can use bash expansion rules & wildcards as well:

if test -d "String*"; then
  echo "A directory starting with 'String' is present"
end

If you need to do something more complex you'll need to use another program like expr.

Maarten Wolzak
  • 2,651
  • 1
  • 19
  • 18
jdeseno
  • 7,753
  • 1
  • 36
  • 34
2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

--> output: String 1 contain string 2

2

In special cases where you want to find whether a word is contained in a long text, you can iterate through the long text with a loop.

found=F
query_word=this
long_string="many many words in this text"
for w in $long_string; do
    if [ "$w" = "$query_word" ]; then
          found=T
          break
    fi
done

This is pure Bourne shell.

Hadrien TOMA
  • 2,375
  • 2
  • 22
  • 32
Kemin Zhou
  • 6,264
  • 2
  • 48
  • 56
0

This is another possible POSIX solution based on this answer, but making it work with special characters, like []*. This is achieved surrounding the substring variable with double quotes.

This is an alternative implementation of this other answer on another thread using only shell builtins. If string is empty the last test would give a false positive, hence we need to test whether substring is empty as well in that case.

#!/bin/sh
# contains(string, substring)
#
# Returns 0 if the specified string contains the specified substring,
# otherwise returns 1.
contains() {
    string="$1"
    substring="$2"
    test -n "$string" || test -z "$substring" && test -z "${string##*"$substring"*}"
}

Or one-liner:

contains() { test -n "$1" || test -z "$2" && test -z "${1##*"$2"*}"; }

Nevertheless, a solution with case like this other answer looks simpler and less error prone.

#!/bin/sh
contains() {
    string="$1"
    substring="$2"
    case "$string" in
        *"$substring"*) true ;;
        *) false ;;
    esac
}

Or one-liner:

contains() { case "$1" in *"$2"*) true ;; *) false ;; esac }

For the tests:

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