Glob Patterns

Filename expansion, wildcards, and pattern matching.

Glob vs Regex

Aspect Glob Regex

Purpose

Match filenames/paths

Match text content

Where used

Shell, find -name, ls

grep, sed, awk, grep -P

* means

Zero or more of ANY char

Zero or more of PREVIOUS char

. means

Literal dot

Any single character

Escaping

\* for literal

\. for literal dot

The Key Insight
# Glob: * means "any characters"
ls *.txt           # All .txt files

# Regex: * means "zero or more of previous"
grep 'a*' file     # Zero or more 'a' characters

Basic Wildcards

Pattern Meaning Example

*

Zero or more characters

*.adoc matches foo.adoc, bar.adoc

?

Exactly one character

?.txt matches a.txt, b.txt (not ab.txt)

[abc]

One of the listed characters

[abc].txt matches a.txt, b.txt, c.txt

[a-z]

One character in range

[a-z].txt matches a.txt through z.txt

[!abc] or [^abc]

NOT one of listed

[!abc].txt matches d.txt, e.txt…​

[!a-z]

NOT in range

[!a-z].txt matches 1.txt, A.txt…​

Examples
# All .adoc files
ls *.adoc

# Single char + .txt
ls ?.txt

# Files starting with a, b, or c
ls [abc]*

# Files NOT starting with a digit
ls [!0-9]*

# Three-letter files
ls ???

# Files with digit in second position
ls ?[0-9]*

Brace expansion {a,b,c} is different from glob - it expands BEFORE shell processes:

# Brace expansion (generates strings)
echo {a,b,c}.txt        # Output: a.txt b.txt c.txt
echo file{1..5}.txt     # Output: file1.txt file2.txt ... file5.txt
echo {a..z}             # Output: a b c ... z

# Combining with glob
ls *.{adoc,md,txt}      # All .adoc, .md, and .txt files
cp file.{txt,bak}       # Copy file.txt to file.bak

# Nested braces
echo {a,b{1,2}}.txt     # Output: a.txt b1.txt b2.txt

# Sequences with step
echo {0..10..2}         # Output: 0 2 4 6 8 10
echo {a..z..2}          # Output: a c e g i k m o q s u w y
Key Difference
# Brace: Expands even if files don't exist
echo {foo,bar}.txt      # Output: foo.txt bar.txt (always)

# Glob: Only matches existing files
echo [fb]*.txt          # Output: (only existing matches)

Extended Glob (extglob)

Enable in bash: shopt -s extglob Zsh has most of these by default.

Pattern Meaning Regex Equivalent

?(pattern)

Zero or one

(pattern)?

*(pattern)

Zero or more

(pattern)*

+(pattern)

One or more

(pattern)+

@(pattern)

Exactly one

(pattern)

!(pattern)

NOT pattern

(negative lookahead)

Examples
# Enable extglob (bash)
shopt -s extglob

# One or more digits
ls +([0-9]).txt         # Matches 1.txt, 123.txt (not .txt)

# Zero or more letters
ls *([a-z]).txt         # Matches .txt, a.txt, abc.txt

# Either .jpg or .png
ls *.@(jpg|png)         # Matches photo.jpg, image.png

# NOT .git directory
ls -d !(*.git)          # Everything except .git

# Files NOT ending in .bak
ls !(*.bak)

# Exactly one letter followed by digits
ls @([a-z])+([0-9]).txt # Matches a1.txt, b123.txt
Zsh Equivalents
# Zsh uses different syntax
setopt EXTENDED_GLOB

# Zero or more
ls (ab)#.txt            # Zsh: # means zero or more

# Negation
ls ^*.bak               # Zsh: ^ means NOT
ls *~*.bak              # Zsh: ~ means except

Recursive Globbing (globstar)

** matches directories recursively.

# Enable in bash
shopt -s globstar

# All .adoc files recursively
ls **/*.adoc

# All files in all subdirectories
ls **/*

# All Python files under src/
ls src/**/*.py

# Zsh has this enabled by default
# Just use **/ for recursive descent
Common Patterns
# Find all config files
ls **/*.{conf,cfg,ini,yaml,yml}

# All shell scripts
ls **/*.{sh,bash,zsh}

# All in specific directory tree
ls ~/atelier/**/*.adoc

# Count files recursively
ls **/*.adoc | wc -l

Glob in find

find -name uses glob patterns:

# Basic glob
find . -name "*.adoc"

# Character class
find . -name "[A-Z]*.adoc"    # Starts with uppercase

# Single character
find . -name "???.txt"         # 3-char name + .txt

# NOT pattern (use !)
find . ! -name "*.bak"

# Multiple patterns (OR)
find . \( -name "*.adoc" -o -name "*.md" \)

# Case insensitive
find . -iname "*.ADOC"         # Matches .adoc, .ADOC, .Adoc
find with Path Patterns
# -path matches full path (including directories)
find . -path "**/test/*.py"

# Exclude directories
find . -path "./.git" -prune -o -name "*.adoc" -print

# Multiple exclusions
find . \( -path "./.git" -o -path "./node_modules" \) -prune \
       -o -name "*.js" -print
find -name vs -regex
# -name: glob pattern, matches filename only
find . -name "CR-*.adoc"

# -regex: regex pattern, matches FULL PATH
find . -regextype posix-extended -regex ".*/CR-[0-9]{4}-[0-9]{2}-[0-9]{2}.*\.adoc"

# Note: -regex must match entire path from .

Dotfiles and Hidden Files

By default, * does NOT match files starting with .

# These do NOT match .bashrc
ls *
ls *.rc

# Explicitly match dotfiles
ls .*           # All dotfiles
ls .*.rc        # Dotfiles ending in .rc
ls .[!.]*       # Dotfiles (exclude . and ..)

# Bash: include dotfiles in glob
shopt -s dotglob
ls *            # Now includes dotfiles

# Zsh: include dotfiles
setopt GLOB_DOTS
ls *            # Now includes dotfiles

Handling No Matches

By default, unmatched glob returns literal pattern:

# If no .xyz files exist:
ls *.xyz        # Output: ls: cannot access '*.xyz': No such file

# Bash: nullglob - expand to nothing if no match
shopt -s nullglob
ls *.xyz        # No error, empty result

# Bash: failglob - error if no match
shopt -s failglob
ls *.xyz        # bash: no match: *.xyz

# Zsh: NULL_GLOB
setopt NULL_GLOB
ls *.xyz        # No error, empty result
Scripting Pattern
# Safe iteration over files
shopt -s nullglob
for f in *.adoc; do
    echo "Processing: $f"
done
# No iteration if no matches (good!)

# Without nullglob:
for f in *.adoc; do
    [[ -e "$f" ]] || continue    # Skip if literal
    echo "Processing: $f"
done

Pattern Comparison Table

Want Glob Regex Example

Any characters

*

.*

.txt vs .\.txt

Single character

?

.

?.txt vs .\.txt

Character set

[abc]

[abc]

Same syntax

Range

[a-z]

[a-z]

Same syntax

Negation

[!abc]

[^abc]

Different syntax

Literal dot

. (literal)

\.

Dot is special in regex

One or more

+(x) (extglob)

x+

Different syntax

Zero or more

*(x) (extglob)

x*

Different meaning!

Alternation

@(a|b) (extglob)

(a|b)

Similar with extglob

Drill 1: Find Change Requests

# All CR files
find ~/atelier/_bibliotheca -name "CR-*.adoc"

# CR files from 2026
find ~/atelier/_bibliotheca -name "CR-2026-*.adoc"

# CR files from March 2026
find ~/atelier/_bibliotheca -name "CR-2026-03-*.adoc"

Drill 2: Find Worklogs

# All worklogs
find ~/atelier/_bibliotheca -name "WRKLOG-*.adoc"

# Today's worklog (substitute date)
find ~/atelier/_bibliotheca -name "WRKLOG-2026-03-12.adoc"

# All 2026 worklogs
ls ~/atelier/_bibliotheca/**/WRKLOG-2026-*.adoc

Drill 3: Config Files

# All config files
find /etc -name "*.conf" 2>/dev/null | head -10

# YAML configs
find . -name "*.y?ml"    # .yml and .yaml

# All config-ish files
find . -name "*.@(conf|cfg|ini|yaml|yml)" 2>/dev/null
# Or without extglob:
find . \( -name "*.conf" -o -name "*.cfg" -o -name "*.ini" -o -name "*.yaml" -o -name "*.yml" \)

Drill 4: Antora Structure

# All antora.yml files
find ~/atelier/_bibliotheca -name "antora.yml"

# All nav.adoc files
find ~/atelier/_bibliotheca -name "nav.adoc"

# All module directories
ls -d ~/atelier/_bibliotheca/*/docs/modules/*/

Drill 5: Backup Files

# Find backup files
find . -name "*.bak" -o -name "*~" -o -name "*.orig"

# Find and delete (careful!)
find . -name "*.bak" -delete

# Find but exclude .git
find . -path "./.git" -prune -o -name "*.bak" -print

Drill 6: Scripts by Type

# All shell scripts
find ~/atelier -name "*.sh"

# Python scripts
find ~/atelier -name "*.py"

# All executable scripts
find ~/atelier -type f -executable -name "*.sh"

# Scripts with specific shebang
find ~/atelier -type f -exec grep -l '^#!/bin/bash' {} \;

Quick Reference

Basic Glob
*       any characters (not .)      *.txt
?       single character            ?.txt
[abc]   one of a, b, c              [abc].txt
[a-z]   range a to z                [a-z].txt
[!abc]  not a, b, c                 [!0-9]*
Brace Expansion
{a,b}       expands to: a b
{1..5}      expands to: 1 2 3 4 5
{a..z}      expands to: a b c ... z
{1..10..2}  expands to: 1 3 5 7 9
Extended Glob (extglob)
?(pat)   zero or one               file?(s).txt
*(pat)   zero or more              file*(s).txt
+(pat)   one or more               file+(s).txt
@(pat)   exactly one               @(foo|bar).txt
!(pat)   not pattern               !(*.bak)
Bash Options
shopt -s extglob     # Enable extended glob
shopt -s globstar    # Enable ** recursive
shopt -s dotglob     # Include dotfiles in *
shopt -s nullglob    # Empty result if no match
find Patterns
-name "*.txt"        # Glob, filename only
-iname "*.txt"       # Case insensitive
-path "**/test/*"    # Glob, full path
-regex ".*\.txt"     # Regex, full path