/commands/bash/check-if-string-contains-substring
bash check if a string contains a substring ([[ $str == *sub* ]])
- 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
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 presentAll 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
| Code | Notes |
|---|---|
| [[ $s == *"$sub"* ]] | Bash, ksh, zsh; fastest, no external process |
| case "$s" in *"$sub"*) ... ;; esac | Works 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 usecase. - macOS bash 3.2.
${str,,}is Bash 4 syntax; on bash 3.2 it fails with bad substitution. Usetr '[:upper:]' '[:lower:]'orshopt -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.