Bash: check if a string contains text, digits, or a prefix (`[[ ]]`, globs, regex)

Bash patterns to see if a string contains a substring or character, if it is only digits, if it starts with a prefix, and how [[ =~ ]] and globs differ— with quoting, empty-string gotchas, and a short locale note.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Bash: check if a string contains text, digits, or a prefix (`[[ ]]`, globs, regex)

Most “bash string contains” tasks boil down to one of three checks: a literal substring, a pattern (digits, letters, “only numbers”), or a prefix/suffix on the string. Bash can do all of that inside [[ ... ]] with either glob patterns or a regex match operator (=~). For ordering and equality without pattern logic, see bash compare strings and bash compare numbers when you move from “contains” to “sorts before/after”.

Tested the [[ ... ]] snippets from this article on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37. One note on locale is in the last section.


Bash: check if a string contains another string (substring)

The usual bash if string contains pattern is a glob on the left-hand side and a quoted needle so empty values or spaces do not break the match:

bash
haystack='foobar'
needle='oba'
if [[ $haystack == *"$needle"* ]]; then
  echo 'substring found'
else
  echo 'not found'
fi

That prints substring found. If needle is empty, the `` collapses in ways you rarely want—guard with [[ -n $needle ]] before testing.


Bash: check if a string contains a digit, or only digits

To answer bash check if string contains a digit anywhere, [[ =~ ]] is concise:

bash
VAR='test1234'
if [[ $VAR =~ [0-9] ]]; then
  echo 'contains at least one digit'
else
  echo 'no ASCII digits'
fi

That prints contains at least one digit. A plain hello run prints no ASCII digits.

For bash check if string is a number in the sense of “only digits” (an integer-shaped string, allowing a leading 0), anchor the regex:

bash
VAR='1234'
if [[ $VAR =~ ^[0-9]+$ ]]; then
  echo 'only digits'
else
  echo 'not only digits'
fi

42 prints only digits; 42a prints not only digits. You can spell the class as ^[[:digit:]]+$ if you prefer POSIX character classes.

These tests care about decimal digits in the string, not whether the value fits in an int for arithmetic—use $(( ... )) validation separately when you need numeric range checks.


Bash string starts with or ends with a prefix

bash string starts with checks are globs on the left:

bash
s='prefix_rest'
if [[ $s == prefix* ]]; then
  echo 'starts with prefix'
fi

Ends with uses a trailing glob:

bash
f='access.log'
if [[ $f == *.log ]]; then
  echo 'ends with .log'
fi

Both snippets print the success branch on the values shown.


Alphanumeric-only strings and “special” characters

To see if a value is letters and digits only (no spaces or punctuation), use [[:alnum:]] anchored to the ends:

bash
VAR='abc123'
if [[ $VAR =~ ^[[:alnum:]]+$ ]]; then
  echo 'alnum only'
else
  echo 'contains other characters'
fi

abc@123 hits the second branch. The inverse—bash check if string contains character classes outside alnum—is often written as “not alnum-only”:

bash
VAR='abc@123'
if [[ ! $VAR =~ ^[[:alnum:]]+$ ]]; then
  echo 'has non-alphanumeric characters'
fi

That prints has non-alphanumeric characters.


Vowels and other letter classes

A small bash check if string contains character example is vowel detection:

bash
VAR='Apple'
if [[ $VAR =~ [AEIOUaeiou] ]]; then
  echo 'contains a vowel'
else
  echo 'no vowels matched'
fi

That prints contains a vowel; rhythm prints no vowels matched with this simple ASCII set.


Locale, regex variables, and habits that save time

  • [[ string =~ regex ]] treats the right side as a regex unless it is stored in a variable—then the variable’s contents are the pattern. If you build patterns dynamically, keep them in a variable to avoid surprises with quoting.
  • Locale changes how [[:alnum:]] treats letters outside ASCII. For strict ASCII tests, run with LC_ALL=C for that snippet. On the test host, café matched ^[[:alnum:]]+$ under the default UTF-8 locale, but not when forced with LC_ALL=C—worth remembering if you script for international text.
  • For heavy validation (email, IDs), consider a small Python or grep -E helper; nested regex in Bash gets hard to read fast.

Summary

Use [[ $haystack == *"$needle"* ]] for the common bash string contains substring case, always quoting the needle. Use [[ $var =~ ... ]] when you need bash check if string contains a digit ([0-9]), bash check if string is number-like with ^[0-9]+$, or letter classes such as vowels. Use [[ $s == prefix* ]] or [[ $s == *.suffix ]] for bash string starts with or ends with checks. For “only letters and digits”, ^[[:alnum:]]+$ is the usual test, and negating it catches broader “non-alnum” content. Match your locale to the data you expect, especially outside ASCII.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …