Pattern Matching
Pattern matching allows you to check if strings match certain patterns. Bash supports both glob patterns and extended patterns for powerful text matching.
Glob Patterns
Basic wildcards you already know:
| Pattern | Matches |
|---|---|
* | Any string (including empty) |
? | Any single character |
[abc] | One of: a, b, or c |
[a-z] | Any character in range |
[!abc] | Not a, b, or c |
#!/bin/bash
FILE="document.txt"
if [[ $FILE == *.txt ]]; then
echo "Text file"
fi
if [[ $FILE == doc* ]]; then
echo "Starts with doc"
fi
Character Classes
Predefined character sets:
| Class | Matches |
|---|---|
[[:alpha:]] | Alphabetic |
[[:digit:]] | Digits |
[[:alnum:]] | Alphanumeric |
[[:space:]] | Whitespace |
[[:upper:]] | Uppercase |
[[:lower:]] | Lowercase |
[[:punct:]] | Punctuation |
#!/bin/bash
CHAR="A"
if [[ $CHAR == [[:upper:]] ]]; then
echo "Uppercase letter"
fi
Extended Patterns
Enable with shopt -s extglob:
| Pattern | Matches |
|---|---|
?(pattern) | Zero or one |
*(pattern) | Zero or more |
+(pattern) | One or more |
@(pattern) | Exactly one |
!(pattern) | Anything except |
#!/bin/bash
shopt -s extglob
FILE="image.jpg"
# Match multiple extensions
if [[ $FILE == *.@(jpg|png|gif) ]]; then
echo "Image file"
fi
# Match optional prefix
if [[ $FILE == ?(old_)image.* ]]; then
echo "Matches with or without 'old_' prefix"
fi
Exercise: Pattern Match
Match file extensions:
Case-Insensitive Matching
#!/bin/bash
shopt -s nocasematch
NAME="ALICE"
if [[ $NAME == alice ]]; then
echo "Match! (case insensitive)"
fi
shopt -u nocasematch # Turn off
Pattern in case Statements
#!/bin/bash
shopt -s extglob
FILE="backup.tar.gz"
case $FILE in
*.tar.gz|*.tgz)
echo "Gzipped tarball"
;;
*.@(zip|rar|7z))
echo "Archive"
;;
*.+(jpg|jpeg|png|gif))
echo "Image"
;;
*)
echo "Unknown type"
;;
esac
Negation Patterns
Match everything except:
#!/bin/bash
shopt -s extglob
# Delete all files except .sh and .md
for file in !(*.sh|*.md); do
echo "Would delete: $file"
done
# Match strings not starting with underscore
if [[ $VAR == !(_*) ]]; then
echo "Doesn't start with underscore"
fi
Pattern Matching in Parameter Expansion
Use patterns in variable substitution:
#!/bin/bash
FILES="file1.txt file2.txt file3.log"
# Remove pattern from beginning
echo "${FILES#* }" # file2.txt file3.log
echo "${FILES##* }" # file3.log
# Remove pattern from end
echo "${FILES% *}" # file1.txt file2.txt
echo "${FILES%% *}" # file1.txt
Exercise: Extended Glob
Use extended patterns:
Matching vs Regex
Patterns are not regex! Compare:
| Glob Pattern | Regex Equivalent |
|---|---|
* | .* |
? | . |
[abc] | [abc] |
*.txt | .*\.txt |
file? | file. |
# Pattern matching (glob)
[[ $FILE == *.txt ]]
# Regex matching
[[ $FILE =~ \.txt$ ]]
Common Patterns
# Filenames
[[ $FILE == *.@(txt|md|doc) ]] # Text documents
[[ $FILE == *.@(jpg|png|gif) ]] # Images
[[ $FILE == *.@(sh|bash) ]] # Shell scripts
# Strings
[[ $VAR == *[[:space:]]* ]] # Contains whitespace
[[ $VAR == [[:alpha:]]* ]] # Starts with letter
[[ $VAR == *[[:digit:]] ]] # Ends with digit
# Versions
[[ $VER == v+([0-9]).+([0-9])* ]] # v1.0, v2.10, etc.
Nullglob and Failglob
Handle pattern expansion behavior:
#!/bin/bash
# nullglob: unmatched patterns expand to nothing
shopt -s nullglob
for file in *.xyz; do # No .xyz files
echo "$file" # Loop doesn't run
done
# failglob: unmatched patterns cause error
shopt -s failglob
ls *.xyz # Error if no matches
Pattern Matching Reference
# Basic globs
* # Any string
? # Any char
[abc] # One of a,b,c
[^abc] # Not a,b,c
[a-z] # Range
# Extended globs (shopt -s extglob)
?(pat) # Zero or one
*(pat) # Zero or more
+(pat) # One or more
@(pat) # Exactly one
!(pat) # Not this
# Character classes
[[:alpha:]] # Letters
[[:digit:]] # Numbers
[[:alnum:]] # Alphanumeric
[[:space:]] # Whitespace
Key Takeaways
- Glob patterns use
*,?, and[...]for matching - Extended globs (
extglob) add?(),*(),+(),@(),!() - Use
[[:class:]]for character classes - Patterns work in
[[...]],case, and file globbing shopt -s nocasematchenables case-insensitive matching- Patterns are NOT regular expressions (different syntax)
- Use
nullglobto handle no-match scenarios gracefully

