How to check if string contains a substring in dash or ash?
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:
-
You can't use
echo
for arbitrary strings. Here, depending on theecho
implementation, it would fail for values of$1
like-n
orfoo\bar
. -
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*
orroot /etc/passwd
. -
grep
without-F
is for regexp matching. You need-F
for fixed string (substring) search (used to be withfgrep
), but again if$2
contains multiple lines, that tellsgrep -F
to search for any of the content of those lines in the input (grep -F $'a\nb'
would look fora
orb
, not the$'a\nb'
string). - In
grep -c $2
, the content of$2
would be taken as an option if it started with-
. You wantgrep -c -e "$2"
orgrep -c -- "$2"
to avoid that. - 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, usereturn
instead. Though both scripts and functions will return with the status of the last run command. -
$(cmd)
expands to the output ofcmd
(and undergoes split+glob here because of the missing quotes again) minus the trailing newline characters. So ifcmd
outputs0\n
,$(cmd)
expands to0
. So running$(cmd)
tries to run the command called0
. If you want to convert the output ofcmd
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 ofcmd
as its exit code). -
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 thatgrep -q
is more efficient as it stops searching after it found one. For ancientgrep
implementations that don't support the (standard)-q
option, you can usefgrep -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
Related videos on Youtube
![Oleksandr](https://i.stack.imgur.com/ePINS.png?s=256&g=1)
Oleksandr
Updated on September 18, 2022Comments
-
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 over 7 years
[[ $string1 == *$string2* ]]
(for substring match) would bezsh
syntax. Inbash
(orksh
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 over 7 years
grep -c
outputs a count of matching lines. When there's no match, that still outputs0
, so[ -n "$(...grep -c...)" ]
would always be true (except for the errors possibly incurred by the missing quotes). -
Stéphane Chazelas over 7 years@Alexandr, see edit.
-
Oleksandr over 7 yearsSté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 over 7 yearspattern moo would suffice, and yes i should have pruned op's grep to remove "-c".