How to check if string contains a substring in dash or ash?

30,742

Solution 1

The Bourne shell had a construct for that. That's the same as in modern sh (which seems more like what you're using, the Bourne shell had no support for $(...); if it was the Bourne shell, you'd get a different error):

case $1 in
  *"$2"*) printf '"%s" is in "%s"\n' "$2" "$1"
esac

If you wanted to use grep, that would be:

if printf '%s\n' "$1" | grep -Fqe "$2"; then
  printf '"%s" is in "%s"\n' "$2" "$1"
fi

Or

if grep -Fqe "$2" << EOF
$1
EOF
then
  printf '"%s" is in "%s"\n' "$2" "$1"
fi

But that would only work properly if $2 contained no newline character.

In any case, you need quotes around most of those parameter expansion. The only place where it's not needed is in that case $1 above, but even then case "$1" wouldn't harm.

A few notes on your code:

  1. You can't use echo for arbitrary strings. Here, depending on the echo implementation, it would fail for values of $1 like -n or foo\bar.
  2. Leaving a parameter expansion unquoted has a very special meaning in shells. You don't want to do that. Try for instance that grep -c $2 with a value of $2 like * or root /etc/passwd.
  3. grep without -F is for regexp matching. You need -F for fixed string (substring) search (used to be with fgrep), but again if $2 contains multiple lines, that tells grep -F to search for any of the content of those lines in the input (grep -F $'a\nb' would look for a or b, not the $'a\nb' string).
  4. In grep -c $2, the content of $2 would be taken as an option if it started with -. You want grep -c -e "$2" or grep -c -- "$2" to avoid that.
  5. You want to use the exit status, not stdout to report boolean conditions. In scripts that's with exit (exit 0 for true, exit 1 (or any non-zero number, but avoid values greater than 120) for false). For functions, use return instead. Though both scripts and functions will return with the status of the last run command.
  6. $(cmd) expands to the output of cmd (and undergoes split+glob here because of the missing quotes again) minus the trailing newline characters. So if cmd outputs 0\n, $(cmd) expands to 0. So running $(cmd) tries to run the command called 0. If you want to convert the output of cmd to an exit status (so it can be used as a boolean), you'd need (exit "$(cmd)"). That starts a subshell that exits with the output of cmd as its exit code).
  7. grep -c counts the number of occurrences. Here you don't need to have the full count, you only need to know if it's found at all (if there's at least one match). For that grep -q is more efficient as it stops searching after it found one. For ancient grep implementations that don't support the (standard) -q option, you can use fgrep -le "$2" > /dev/null. With -l, fgrep also stops upon the first match, but outputs the file name (which we discard here by redirecting the output to /dev/null).

Solution 2

A portable (dash,bash,ksh, etc) script containing a function:

#!/bin/sh

contains() {  if    [ "$1" ] &&            # Is there a source string.
                    [ "$2" ] &&            # Is there a substring.
                    [ -z "${1##*"$2"*}" ]; # Test substring in source.
              then  echo 0;                # Print a "0" for a match.
              else  echo 1;                # Print a "1" if no match.
              fi;
            }

contains "$1" "$2"

Where 0 means "contains" (true value).

which will test as:

 $ contains "test a simple line" simpl
 1

 $ contains "One sentence" "No more"
 0

Solution 3

bash supports this natively;

$ string1="abcmoocow"
$ string2="moo"
$ if [[ $string1 == *$string2* ]]; then echo "Match"; else echo "No Match"; fi
Match
$ string1="abccrowcow"
$ if [[ $string1 == *$string2* ]]; then echo "Match"; else echo "No Match"; fi
No Match

also to correct your example; change;

if $(echo $1 | grep -c $2) ; then

to become:

if [ -n "$(echo $1 | grep -c $2)" ]; then

(checks if the grep returns any data)

also note it is also possible to work with the exit code;

$ echo bob | grep "o" &>/dev/null; echo $?
0
$ echo bob | grep "z" &>/dev/null; echo $?
1
Share:
30,742

Related videos on Youtube

Oleksandr
Author by

Oleksandr

Updated on September 18, 2022

Comments

  • Oleksandr
    Oleksandr almost 2 years

    Here is what I am tring:

    #!/bin/sh
    contains() {
        if $(echo $1 | grep -c $2) ; then
            echo "0" # contains
        else
            echo "1" # not contains
        fi
    }
    
    myString=$1
    mySubsting=$2
    
    contains $myString '$mySubsting'
    

    Here is an example of execution:

    # sh ./myScript abcdef bc
    ./myTest: line 3: 0: command not found
    1
    

    EDITED:

    Original question was: How to check if string contains a substring in Bourne Shell?

    Here is what I am tring:

      #!/bin/sh
      if echo $1 | grep -q $2
      then
        echo "0"
      else
        echo "1"
      fi
    

    Here is an example of execution:

    $ sh ./myTest "$(systemctl status ntp)" "Active: active"
    grep: active: No such file or directory
    1
    

    How to properly check if one string contains another?

  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    [[ $string1 == *$string2* ]] (for substring match) would be zsh syntax. In bash (or ksh where that syntax comes from), you'd need [[ $string1 == *"$string2"* ]] instead as otherwise, the content of $string2 would be taken as a pattern.
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    grep -c outputs a count of matching lines. When there's no match, that still outputs 0, so [ -n "$(...grep -c...)" ] would always be true (except for the errors possibly incurred by the missing quotes).
  • Stéphane Chazelas
    Stéphane Chazelas over 7 years
    @Alexandr, see edit.
  • Oleksandr
    Oleksandr over 7 years
    Stéphane Chazelas, wow, so much information. I am novice to linux scripting so I haven't known those things. Thank you for explanation one more time. It is just amazing
  • mikejonesey
    mikejonesey over 7 years
    pattern moo would suffice, and yes i should have pruned op's grep to remove "-c".