Right, let’s talk about making your shell do the heavy lifting. You’re not going to type out every single filename one by one, are you? Of course not. You’re smarter than that, and the shell is too. This is where “globbing” comes in—the process of using wildcard patterns to match filenames. It’s called globbing because of the original glob() function that handled it, and it’s one of those wonderfully simple yet powerful concepts that, once you master it, you’ll wonder how you ever lived without it.

First, a crucial public service announcement: This is not regular expressions. I know, I know, your brain is itching to use .* for everything. Resist the urge. Globbing patterns are a different, simpler language for a different job. Using regex for filenames is like using a flamethrower to light a candle—impressive, but often overkill and messy.

The Core Cast of Characters

Let’s meet the basic wildcards. They’re the bread and butter of this whole operation.

  • * (The Star): This matches any string of any length, including an empty string. It’s your “anything goes” wildcard. Think of it as a stand-in for “whatever, man.”
  • ? (The Question Mark): This matches any single character. It’s precise. It’s picky. It’s for when you know the structure of a filename but maybe one character is different.
  • [] (Character Classes): This matches any one of the characters enclosed within the brackets. [aeiou] would match a single vowel. You can also use ranges: [a-z] matches any lowercase letter, [0-9] matches any digit.

Let’s see them in action. Imagine a directory with these files: cat.txt, dog.txt, can.txt, car.txt, bat.txt.

# Let's list everything that ends with .txt
ls *.txt

# Now, let's find all three-letter filenames that start with 'c' and end with 't'
ls c?t.txt
# This would match: cat.txt, but NOT can.txt or car.txt (too many letters) or bat.txt (wrong first letter)

# Now, let's find files that start with either 'c' or 'b'
ls [cb]at.txt
# This matches: cat.txt, bat.txt

Brace Expansion: The Globbing Power-Up

Now, here’s where things get spicy. Brace expansion {} is a separate mechanism that happens before globbing, and it’s gloriously weird. It generates sets of strings. The shell sees the braces and literally expands them out into multiple arguments before it even thinks about applying wildcards.

This is your best friend for avoiding tedious repetition. The designers got this one very, very right.

# Instead of this:
cp script.sh script.sh.backup

# You can do this:
cp script.sh{,.backup}
# The shell expands this to: cp script.sh script.sh.backup

# Need to create a whole project structure? Easy.
mkdir -p src/{main,test}/{java,resources}
# This creates:
# src/
# ├── main/
# │   ├── java/
# │   └── resources/
# └── test/
#     ├── java/
#     └── resources/

# It works anywhere, not just with mkdir.
echo file_{A,C,23}.txt
# Output: file_A.txt file_C.txt file_23.txt

The beauty here is that it doesn’t even matter if the files exist yet. The shell is just generating text. It’s a text manipulation tool, not a file matching tool.

Common Pitfalls and “Oh, C’mon!” Moments

  1. The Hidden File Problem: This one gets everyone. The wildcard * does not match files that start with a dot (.). These are “hidden” files in Unix. So if you rm * and think you’ve nuked everything, you’ll be left with a bunch of .config, .bash_history, and other secrets still lurking. To get them, you need to be explicit: rm .*. (BE CAREFUL WITH rm .*! It will also match ../ (the parent directory) which is a fantastic way to have a very, very bad day. Always use -i for interactive mode when globbing with rm or test with echo first).

  2. The Nullglob Problem: What happens if your pattern matches nothing? By default, the shell gets literal and says, “Fine, you want a file named *.jpg? Here, I’ll just pass the pattern itself as an argument.” This is why you sometimes see commands like cat *.jpg fail with cat: *.jpg: No such file or directory—it’s trying to open a file literally called *.jpg. The nullglob shell option (in bash, shopt -s nullglob) fixes this by making the pattern simply vanish if it has no matches. It’s often what you actually want.

  3. Spaces in Filenames: The eternal enemy. Globbing handles this correctly—it expands each matched filename as a separate argument. The problem is what you do with that list. If you just jam it into a command without quoting, the spaces will still cause chaos. Always quote your variables that contain glob results.

    # DANGER: if a filename has a space, like "my file.txt", this will break
    for file in *.txt; do
        rm $file
    done
    # The shell sees: rm my file.txt → it tries to remove 'my' and 'file.txt'
    
    # SAFETY: quote the variable!
    for file in *.txt; do
        rm "$file"
    done
    # The shell sees: rm "my file.txt" → it removes the one correct file.
    

So there you have it. Use * for “everything,” ? for “one thing,” [] for “one of these things,” and {} to avoid typing the same thing over and over. It’s a simple toolkit, but it’s the foundation of making the shell work for you instead of the other way around. Now go automate something.