191

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
1
  • 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. Commented Nov 6, 2014 at 22:03

15 Answers 15

231

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 '*\`[]' '\`'
Sign up to request clarification or add additional context in comments.

7 Comments

This doesn't work for me if the substring contains backslashes. As usual, substring="$( printf '%q' "$2" )" saves the day.
What about using wildcards? [[ $haystack == *"My needle"* ]]
What's wrong is that double-brackets aren't POSIX, which was the premise of the question, @Pablo.
This does not work with special characters like []. See my answer stackoverflow.com/a/54490453/712666.
We have to double quote the matching "$word". shellcheck.net/wiki/SC2295
|
126

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.

Comments

55
#!/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

2 Comments

Change grep -q "$2" to grep -q "$2" > /dev/null to avoid undesired output.
Change grep -q "$2" to grep -q "$2" 2>/dev/null to avoid undesired output.
37

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.

3 Comments

Don't quote the regex, or it will not work in general. For example, try [[ test =~ "test.*" ]] vs. [[ test =~ test.* ]].
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.
This is Bash, not POSIX sh as the question asks.
20
case $(pwd) in
  *path) echo "ends with path";;
  path*) echo "starts with path";;
  *path*) echo "contains path";;
  *) echo "this is the default";;
esac

Comments

10

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

4 Comments

On sh I received "unknown operand" with this. Works with Bash though.
[[ is not POSIX
This works in the IBM z/OS USS POSIX shell.
It doesn't work in Jenkins for me though
6

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

 if expr "$link" : '/.*' > /dev/null; then
    PRG="$link"
  else
    PRG=`dirname "$PRG"`/"$link"
  fi

Comments

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.

2 Comments

Wouldn't it be possible to return the result of test? As in return [ ${#haystack} -eq ${#1} ]?
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.
2
test $(echo "stringcontain" "ingcon" |awk '{ print index($1, $2) }') -gt 0 && echo "String 1 contain string 2"

--> output: String 1 contain string 2

Comments

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.

1 Comment

There seems to be a spurious double quote (") in your second example.
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.

Comments

1

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

1 Comment

Thank you for the switch one-liner and tests, it's glorious.
1
STRING="String to test"
SUBSTRING="test"

if [ $(echo $STRING|grep $SUBSTRING|wc -l) -ne 0 ]
then
    echo "exists"
fi

Comments

0

Since I didn't see the example of how I've done it... Here's my contribution.

#!/bin/sh
thiscmd="java $@"
javasubstr=" -jar "
if [ -z "${thiscmd##*$javasubstr*}" ]; then
  #it exists
fi

Comments

0

To check if a variable contains a fixed-string i'm using this method :

if [ "$ALPHABET" != "${ALPHABET#*CDDE}" ]; then
    echo "the ALPHABET contains 'CDDE' string"
else
    echo "there is no 'CDDE' string in ALPHABET variable"
fi

To understand it more easily :

#!/bin/sh

LANG=C

ALPHABET="AABBCCDDEE"

EXAMPLE_1="${ALPHABET#*B}"
EXAMPLE_2="${ALPHABET##*B}"
EXAMPLE_3="${ALPHABET%B*}"
EXAMPLE_4="${ALPHABET%%B*}"

# The flag '#'  operates for the shortest prefix
# The flag '##' operates for the longest prefix
# The flag '%'  operates for the shortest suffix
# The flag '%%' operates for the longest suffix

echo $EXAMPLE_1 # BCCDDEE
echo $EXAMPLE_2 # CCDDEE
echo $EXAMPLE_3 # AAB
echo $EXAMPLE_4 # AA

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.