Skip to content

/commands/bash/check-if-string-contains-substring

bash check if a string contains a substring ([[ $str == *sub* ]])

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
tool
Bash
versions
Bash 3.2+, POSIX sh: case
os
linux, macos, windows

In bash, [[ $string == *"$substring"* ]] is true when $substring occurs anywhere in $string. The * on both sides are glob wildcards and must stay outside the quotes; the quotes around the variable make its content literal. For POSIX sh, use a case statement with the same pattern, and for regular expressions use [[ $string =~ regex ]].

Ways to test

bash
str="Error: connection refused (port 5432)"
sub="refused"

[[ $str == *"$sub"* ]] && echo "1. [[ == *sub* ]]: found"
[[ $str == "*$sub*" ]] || echo "2. quoted pattern: not found (compares the literal *refused*)"

case "$str" in
  *"$sub"*) echo "3. case: found" ;;
  *) echo "3. case: not found" ;;
esac

[[ $str =~ port\ ([0-9]+) ]] && echo "4. regex: port is ${BASH_REMATCH[1]}"

[[ ${str,,} == *"error"* ]] && echo "5. case-insensitive with \${str,,}: found"

if grep -qF -- "$sub" <<< "$str"; then echo "6. grep -qF: found"; fi

[[ $str == *"timeout"* ]] || echo "7. timeout: not present"

Output

1. [[ == *sub* ]]: found
2. quoted pattern: not found (compares the literal *refused*)
3. case: found
4. regex: port is 5432
5. case-insensitive with ${str,,}: found
6. grep -qF: found
7. timeout: not present

All seven tests behave as their labels say. The second line is the most common mistake: with the whole pattern in quotes, bash looks for the literal characters *refused*.

Options

CodeNotes
[[ $s == *"$sub"* ]]Bash, ksh, zsh; fastest, no external process
case "$s" in *"$sub"*) ... ;; esacWorks in every POSIX shell, including sh and dash
[[ $s =~ regex ]]Extended regular expression; captures in BASH_REMATCH; keep the regex unquoted
[[ ${s,,} == *"${sub,,}"* ]]Case-insensitive (Bash 4+)
grep -qF -- "$sub" <<< "$s"Fixed-string search through grep; slower, one process per test
[[ $s == "$sub"* ]] / [[ $s == *"$sub" ]]Starts with / ends with

Pitfalls

  • [ ] instead of [[ ]]. In single brackets == does no pattern matching, and [ $str == *sub* ] lets the shell expand *sub* against file names, which can fail with too many arguments.
  • sh scripts. A script run with sh script.sh (dash on Debian and Ubuntu) fails on [[ with [[: not found. Run it with bash or use case.
  • macOS bash 3.2. ${str,,} is Bash 4 syntax; on bash 3.2 it fails with bad substitution. Use tr '[:upper:]' '[:lower:]' or shopt -s nocasematch.
  • Quoted regex. [[ $s =~ "port [0-9]+" ]] matches the literal text; put the regex in a variable (re='port ([0-9]+)'; [[ $s =~ $re ]]) to keep it readable.
  • Special characters in $sub. With the variable quoted, *, ? and [ in it are matched literally, which is usually what you want.

Confirm it worked

Print a message in both branches (&& echo found || echo missing) and test with a value that contains the substring, one that does not, and one that differs only in case.

FAQ

Why does [[ $str == "*sub*" ]] never match?

Quoting the right-hand side turns the pattern into a literal string, so bash compares with the text *sub* including the asterisks. Leave the * unquoted and quote only the variable part: [[ $str == *"$sub"* ]].

How do I make the check case-insensitive?

Lowercase both sides with ${str,,} and ${sub,,} (Bash 4+), or set shopt -s nocasematch before the [[ ]] test and unset it afterwards.

Does this work in sh or dash?

No. [[ ]] is bash (and zsh/ksh) syntax. In POSIX sh use case "$str" in *"$sub"*) ... ;; esac, which works in every shell.

Sources