To check if a string contains a substring in Bash, wrap the substring in wildcards and compare it inside double brackets: [[ "$string" == *"substring"* ]]. This is the most readable and commonly recommended method, though Bash offers several other approaches depending on whether you need POSIX portability, regex matching, or grep’s extra features.
The Recommended Method: Wildcard Matching
STR='GNU/Linux is an operating system'
SUB='Linux'
if [[ "$STR" == *"$SUB"* ]]; then
echo "It is there."
fi
The asterisks act as wildcards matching zero or more characters on either side, so the condition is true whenever $SUB appears anywhere inside $STR. This requires [[ ]] rather than [ ], which means it needs a Bash-compatible shell — it isn’t POSIX sh compliant.
Using a Case Statement
The case statement supports the same wildcard pattern and reads cleanly for this kind of check:
STR='GNU/Linux is an operating system'
SUB='Linux'
case "$STR" in
*"$SUB"*) echo "It is there." ;;
esac
This is a solid alternative if you’re already using case elsewhere in your script for other conditions.
Using the Regex Match Operator
Bash’s =~ operator treats the right-hand side as a POSIX extended regular expression, which gives you more flexibility than a plain wildcard:
string='Hello world!'
if [[ $string =~ "world" ]]; then
echo "Found 'world' in string."
fi
Because it’s a full regex engine, you can also match more complex patterns — for example, checking for either of two characters:
string='Hello world!'
if [[ $string =~ !|\? ]]; then
echo "The string matches!"
fi
Reach for =~ when the wildcard method isn’t expressive enough for what you’re trying to match.
Using grep
If you want to lean on grep's features — like case-insensitive matching (-i) or fixed-string search (-F) — you can pipe the string into it and check the exit status with -q:
STR='GNU/Linux is an operating system'
SUB='Linux'
if grep -q "$SUB" <<< "$STR"; then
echo "It is there."
fi
The -q flag suppresses grep’s normal output and just returns an exit code: 0 if the pattern was found, non-zero otherwise. This approach spawns an external process, so it’s typically slower than the pure-Bash methods above — worth it only when you specifically need grep’s extra options.
POSIX-Portable Alternative
If your script needs to run in a strictly POSIX-compliant shell (not just Bash), avoid [[ ]] entirely and use parameter expansion instead:
pineapples="pineapples"
apple="apple"
if [ -z "${pineapples##*$apple*}" ]; then
echo "Found it"
fi
This works by stripping everything matching the pattern from the string; if the result is empty, the substring was present. It’s less readable but avoids Bash-only syntax.
Which Method Should You Use?
- Most scripts:
[[ "$STR" == *"$SUB"* ]] — readable, fast, no subshell needed.
- Need POSIX
sh compatibility: the ${var##*pattern*} parameter expansion trick.
- Need real regex features: the
=~ operator.
- Need grep-specific options like case-insensitivity: pipe into
grep -q.
Join The Discussion
Substring checks come up constantly in shell scripting, and most people settle into one preferred method early on. Which approach do you reach for by default — wildcard matching, =~, or grep — and has your choice ever changed because of a portability requirement or a tricky edge case? Share your go-to pattern or any gotchas you’ve run into.