Programming

Check if a String Contains a Substring in Bash - Quick Guide

Fast, idiomatic ways to check if a string contains a substring in Bash. Use [[ *substring* ]], case, or =~ for regex. Also covers quoting and portability.

1 answer 6 views

How to check if a string contains a substring in Bash

I have a string in Bash:

bash
string="My string"

How can I test if it contains another string? I tried using:

bash
if [ $string ?? 'foo' ]; then
 echo "It's there!"
fi

Where ?? is my unknown operator. Is using echo and grep the correct approach?

bash
if echo "$string" | grep 'foo'; then
 echo "It's there!"
fi

That seems a bit clumsy. What’s the proper way to check for substrings in Bash?

To do a bash check substring (test whether a bash string contains a substring), use Bash’s built-in pattern matching: [[ "$string" == *"$substring"* ]] or a case statement — both avoid forking grep. Use [[ $string =~ regex ]] for real regular-expression matching (captures go to BASH_REMATCH); grep -q or awk work for portability but are slower and spawn external processes.


Contents


Quick examples (bash substring, bash string contains)

If you have:

bash
string="My string"
substring="foo"

The simplest, most idiomatic Bash check is:

bash
if [[ "$string" == *"$substring"* ]]; then
 echo "It's there!"
fi
  • [[ ... ]] is a Bash conditional that understands pattern matching.
  • *"$substring"* is a glob pattern: “anything, then the literal substring, then anything.”

POSIX-portable alternative (works in plain /bin/sh):

bash
case "$string" in
 *"$substring"*) echo "It's there!";;
 *) echo "Not found";;
esac

If you prefer an external-tool approach (portable but slower), use grep -qF (fixed-string, quiet):

bash
# portable-ish, but forks grep
if printf '%s\n' "$string" | grep -qF -- "$substring"; then
 echo "It's there!"
fi

# or with bash's here-string (bash-only)
if grep -qF -- "$substring" <<< "$string"; then
 echo "It's there!"
fi

For quick reusable checks, a small function:

bash
contains() {
 case "$1" in
 *"$2"*) return 0 ;;
 *) return 1 ;;
 esac
}

if contains "$string" "foo"; then
 echo "found"
fi

For more examples and explanation of pattern matching, see the guide at https://docs.vultr.com/how-to-check-for-a-substring-in-bash and a practical how‑to at https://www.delftstack.com/howto/linux/string-contains-a-substring-in-bash/.


Why [[ ]] / case are better than echo | grep

Why not just echo "$string" | grep 'foo' every time? Two reasons:

  • Performance: [[ ... ]] and case are shell builtins — no new process is spawned. In tight loops or scripts that run often, avoiding grep saves CPU and latency. See a practical test and discussion at https://www.howtogeek.com/i-found-using-grep-or-sed-in-bash-scripts-is-painfully-slow-but-heres-how-i-fixed-it/.
  • Simplicity and safety: builtin patterns avoid quoting pitfalls and are easier to read. grep interprets its pattern as a regex by default; use -F to treat it as a fixed string. Also, echo can mangle some inputs; prefer printf '%s\n' if you pipe to external tools.

Use [[ "$string" == *"$substring"* ]] for literal substring checks in Bash, and case for scripts that must run under /bin/sh.


Regex matching with =~ and capturing

If you need regular expressions (not simple substring), use =~:

bash
string="version 1.2.3"
if [[ $string =~ ([0-9]+.[0-9]+.[0-9]+) ]]; then
 echo "Version: ${BASH_REMATCH[1]}"
fi

Notes:

  • The right-hand side is an extended regular expression. Do NOT quote the regex; quoting makes it a literal string and prevents regex parsing.
  • Matches are stored in the array BASH_REMATCH ([0] is the whole match, [1] the first capture group, …).

If your regex comes from user input (you want a literal match but are using =~), escape regex meta-characters first:

bash
escape_regex() {
 printf '%s' "$1" | sed -e 's/[][\/.^$*+?{}()|]/\\&/g'
}

needle_regex=$(escape_regex "$substring")
if [[ $string =~ $needle_regex ]]; then
 echo "Found (literal match via =~)"
fi

(That sed expression escapes characters that are special in POSIX ERE.)

For additional examples and parameter-expansion tricks, see https://www.delftstack.com/howto/linux/string-contains-a-substring-in-bash/ and https://www.namehero.com/blog/bash-string-comparison-the-comprehensive-guide/.


Edge cases and pitfalls (quoting, special characters, empty substring)

  • Quoting: In [[ ]] word-splitting and pathname expansion don’t happen, but you should still write *"$substring"* (quote the variable) so a substring containing spaces or glob characters is treated safely.
  • Single-bracket [ ... ] (the test builtin) is NOT reliable for pattern matching. Prefer [[ ... ]] or case for substring checks.
  • Empty needle: substring=""[[ "$string" == *""* ]] returns true. If you don’t want that, check for an empty needle first ([ -z "$substring" ] && return 1).
  • Meta-characters: If $substring contains *, ?, [ etc., quoting inside the pattern (*"$substring"*) prevents those characters from being treated as glob operators; with =~ you must escape regex metas.
  • Newlines / binary data: pattern matching works on the variable’s contents; if dealing with multi-line or binary blobs, external tools may behave differently.
  • Portability: [[ and ${var,,} (lowercasing) require Bash. Use case and tr for POSIX portability.

Case-insensitive checks and POSIX portability

Want case-insensitive substring checks?

  • Bash-native (fast):
bash
shopt -s nocasematch
if [[ "$string" == *"$substring"* ]]; then
 echo "found (case-insensitive)"
fi
shopt -u nocasematch
  • Bash 4+ lowercasing:
bash
if [[ "${string,,}" == *"${substring,,}"* ]]; then
 echo found
fi
  • POSIX (portable) approach using tr + case:
bash
lc_string=$(printf '%s' "$string" | tr '[:upper:]' '[:lower:]')
lc_sub=$(printf '%s' "$substring" | tr '[:upper:]' '[:lower:]')
case "$lc_string" in
 *"$lc_sub"*) echo "found";;
esac

Use the approach that matches your script’s portability requirements.


Performance and best practices (bash check substring)

  • For literal substring checks in Bash: prefer [[ "$string" == *"$substring"* ]] — fast and readable.
  • For shell portability: use case.
  • For regex needs: use [[ $string =~ regex ]] and BASH_REMATCH for captures; escape user input before inserting into a regex.
  • Use grep -qF -- when you must rely on external tools or search files; add LC_ALL=C for faster ASCII-only searches when appropriate.
  • Always quote variable expansions when they might contain spaces or special characters.
  • Encapsulate repeated logic in a small function (see contains() above); it makes tests clearer and centralizes escaping/normalization.

Sources


Conclusion

For checking whether a string contains a substring in Bash, prefer built-ins: [[ "$string" == *"$substring"* ]] (or case) for simple literal checks and [[ $string =~ regex ]] for regex matches. These approaches are cleaner and faster than piping to grep in most scripts; use grep -qF when portability or file-searching requires an external tool. In short: use Bash pattern matching for typical bash substring checks, reserve grep for situations where you specifically need its features.

Authors
Verified by moderation
Moderation
Check if a String Contains a Substring in Bash - Quick Guide