🎁 Give the #1 gift request of 2024... a ZTM membership gift card! 🎁

Beginners Guide to Bash Regex (With Code Examples)

Andrei Dumitrescu
Andrei Dumitrescu
hero image

Have you ever had to search for a specific error in a sea of log files? Or maybe you’ve faced the headache of renaming dozens (or hundreds) of files manually — one by one?

It’s frustrating, right?

But imagine if you could do all that with a single line of code. No manual scanning. No repetitive renaming. Just one command, and it’s done. That’s the power of Bash regex.

It’s like having a magic tool that can instantly find, extract, and transform text — from tracking down errors in logs to organizing file names in seconds. Once you learn it, you’ll wonder how you ever lived without it.

In this guide, I’ll walk you through Bash regex step-by-step, so that by the end, you’ll know how to automate tasks that used to take hours and turn them into quick wins with just a few keystrokes.

Sidenote: Want to dive deeper into Bash? Check out my complete BASH scripting course:

learn bash

You'll learn Shell Scripting fundamentals, master the command line, and get the practice and experience you need to go from beginner to being able to get hired as a DevOps Engineer, SysAdmin, or Network Engineer!

I guarantee that this is the most comprehensive and up-to-date online resource to learn Bash Scripting.

With that out of the way, let’s dive into this 5-minute tutorial…

What is Bash Regex and why does it matter?

Bash regex (short for "regular expression") is an incredibly helpful tool, because it lets you search for patterns, and not just exact words.

So instead of trying to find ‘error,’ you can tell it to find anything that looks like ‘error’ in any format. This lets you match lowercase, uppercase, or variations like "error123" and "ERROR_LOG."

Handy right?

Without regex, you’d have to scan files line-by-line or write clunky scripts to extract the data you need, but Regex cuts through all that. It’s faster, cleaner, and way more efficient. Instead of hardcoding every possible search, you define a pattern and let regex do the heavy lifting.

The core Regex building blocks

Mastering Bash regex starts with understanding its 5 core building blocks. Each of these building blocks adds a new layer of control, letting you move from simple searches to complex pattern matching.

Here’s the mile-high breakdown:

  1. Literals — Match exact text, letter for letter
  2. Metacharacters — Special symbols like . (wildcard) and * (repetition) that expand your matching power
  3. Character Classes — Match groups or ranges of characters like [a-z] for all lowercase letters
  4. Grouping and Capturing — Extract or reference chunks of text from your matches
  5. Escaping Special Characters — Force regex to treat special characters (like . or $) as plain text

Let’s walk through each one with examples.

Regex building block #1: Literals

Let’s start with the simplest concept: literals. These are exactly what they sound like — plain text matches. If you want to find the word "error" in a log file, you can just search for "error" exactly as it is. No special symbols or complex patterns are needed.

For example

echo "error log" | grep 'error'

This command finds and prints any line that contains "error." Since "error" is a literal match, there’s no confusion — it matches exactly as you wrote it.

This concept might seem basic, but it’s the foundation for everything else. If you ever need to search through dozens (or hundreds) of files for exact matches, this is the tool for the job. It’s fast, direct, and doesn’t require you to learn anything complicated.

Later, you’ll see how to build on this concept to handle more complex scenarios.

Regex building block #2: Metacharacters

If literals are the "what-you-see-is-what-you-get" part of regex, metacharacters are the wildcards that make regex powerful. These special characters let you match patterns instead of fixed text.

With them, you can match the start or end of a line, capture optional characters, and even search for multiple possibilities at once.

dot (.)

Take the dot (.) for example. It’s like a wildcard that matches any character except a newline. So, if you search for c.t, it’ll match words like "cat," "cot," and "cut." It’s perfect when you know part of a word but not the whole thing.

caret (^)

Another key metacharacter is the caret (^), which tells the regex to look at the start of a line.

For example

If you wanted to find log entries that only start with "ERROR," you’d use ^ERROR.

Easy right?

dollar sign ($)

Then there’s the dollar sign ($), which does the opposite — it looks for matches at the end of a line.

For example

If you want to find filenames that end in "log," you’d search for log$. This makes it simple to filter .log files from a list of filenames.

asterisk (*)

Some metacharacters control how characters repeat. The asterisk (*) means "zero or more of the previous character," so do*g matches "dg," "dog," and "dooog."

plus (+)

But if you need to make sure there’s at least one of something, you’d use the plus (+) instead.

For example, do+g matches "dog" and "dooog," but it won’t match "dg" since the plus requires at least one "o."

question mark (?)

You can also mark optional characters using the question mark (?), which tells regex "This character might be there, but it’s optional." A perfect example is matching both the American "color" and British "colour" with colou?r.

pipe (|)

Sometimes you’ll want to look for multiple possibilities in one go. That’s where the pipe (|) comes in. It’s like saying "this OR that."

If you search for (cat|dog), it matches both "cat" and "dog". It’s perfect when you’re tracking multiple keywords in logs or matching different file extensions.

parentheses ( )

To keep things organized, you can group patterns together with parentheses ( ). This makes it possible to apply other rules (like *, +, or ?) to an entire group instead of a single character.

For example, suppose you want to match "sunset" but not "sunrise" or "sun". Without parentheses, it’s tricky. But with parentheses, you can group "sun" and combine it with "set" like this:

echo "sunrise sunset sun" | grep -E '(sun)set'

The parentheses (sun) group "sun" together, allowing the "set" to be added directly after the group. This matches "sunset", but it won’t match "sunrise" or "sun" since "set" is required after the group.

Without parentheses, you'd need to write something like s(unset|rise), which is more complex. But by grouping "sun", you can combine it with other characters efficiently.

backslash (\)

Finally, if you need to match an actual metacharacter (like a dot or a dollar sign), you have to "escape" it with a backslash (). This tells regex to treat it as plain text instead of a special symbol.

So, if you want to match a filename like file.txt, you’d search for file\.txt since \. tells regex to look for a literal dot, not "any character."

TL;DR

Metacharacters might seem like a lot to remember, but they’re some of the most useful tools you’ll use. The more you use them, the more you’ll recognize when you need them.

Next, I’ll show you how to use character classes to match entire groups of characters at once.

Regex building block #3: Character classes

Sometimes you don’t want to search for a single character, but a range of possible characters. That’s where character classes come in. Instead of listing every possible match, you can create a simple pattern that covers them all at once.

Here’s how it works.

Character match

If you want to match the letter "a", "b", or "c", you don’t have to write three separate searches. Instead, you can use [abc]. This tells regex, "match one character, as long as it’s 'a', 'b', or 'c'."

So, if you search for gr[ae]y, it will match both "gray" and "grey" — useful when different spellings exist.

Character exclusion

You can also exclude characters. If you want to match anything except vowels, you’d use [^aeiou]. The ^ inside the brackets means "not," so [^aeiou] matches any character that’s not a vowel. This is handy when you’re looking for consonants or need to exclude specific characters.

Matching letter ranges

Another common use case is matching letter ranges. Instead of writing every letter from "a" to "z," you can simply use [a-z]. This matches all lowercase letters.

Want uppercase letters? Just change it to [A-Z]. And if you want both lowercase and uppercase, combine them: [a-zA-Z]. The same concept works for numbers. If you want to match any digit from 0 to 9, you can use [0-9].

But what if you want to match both letters and numbers? You’d combine them like this: [a-zA-Z0-9]. This is useful for matching alphanumeric strings like usernames, file names, or product codes.

Character classes are one of the most flexible parts of regex because they give you precise control over what to match. If you ever find yourself thinking, "I need to match letters, but only lowercase ones," or "I want to match digits, but no letters," character classes have you covered.

Regex building block #4: Grouping and capturing

Up until now, you’ve been matching characters, patterns, and ranges — but what if you want to match entire chunks of text or reuse parts of the match later? That’s where grouping and capturing come in. By using parentheses ( ) like we mentioned earlier, you can organize multiple parts of your pattern and treat them as one.

But there's a difference:

  • Grouping helps you control which parts of the pattern are matched together
  • Capturing takes it further by letting you save what was matched so it can be reused later

Grouping and capturing are essential when you need to extract specific details like dates from log entries, file extensions from filenames, or user IDs from URLs. If you’ve ever had to pull out just the date from a log or transform part of a filename, this is how it’s done.

Matching multiple possibilities with the OR operator

One of the most useful tricks is using the OR operator inside parentheses.

For example

Imagine you’re scanning a log file for lines that contain either "error" or "warning." Instead of running two separate searches, you can combine them into one:

grep -E '(error|warning)' system.log

This command will find any line that contains "error" or "warning." The (error|warning) part means, "Look for either 'error' OR 'warning'." This makes it easy to track multiple error types in one go.

Grouping parts of a pattern

Grouping also lets you target specific parts of a pattern. For example, suppose you’re looking for the words "cat" or "dog," but only if they’re immediately followed by "house." You could write it like this:

echo "cathouse doghouse mousehouse" | grep -E '(cat|dog)house'

This will match "cathouse" and "doghouse" but not "mousehouse" because only "cat" and "dog" are part of the group. The parentheses (cat|dog) group "cat" and "dog" together, while house comes right after, creating one combined search.

Capturing and reusing matched text

Capturing works a little differently. Instead of just matching the pattern, capturing actually saves the match for later use.

This is essential when you need to extract specific parts of a string, like pulling only the date from a log entry. If you’re working with tools like sed or awk, you can refer back to these "captured groups" later.

For example, suppose you want to pull out just the date from a log entry like this:

echo "2023-12-19 ERROR Disk full" | grep -Eo '([0-9]{4}-[0-9]{2}-[0-9]{2})'
  • This pattern captures the date 2023-12-19 from the line
  • While the part ([0-9]{4}-[0-9]{2}-[0-9]{2}) tells regex to group this entire date as a single chunk
  • Finally, the parentheses ( ) "capture" the date so it can be reused or referenced later

Regex building block #5: Escaping special characters

Some characters in regex have special meanings, like . (dot), * (asterisk), and | (pipe).

But what if you want to match the actual character itself — like a literal period in a filename? That’s where escaping comes in.

When you "escape" a character, you tell regex to treat it as plain text instead of a special symbol. To do this, you add a backslash (\) in front of it.

For example

Normally, a dot (.) matches any character. But if you want to match a literal dot (like the one in file.txt), you’d write it as \..

Why do you need to escape characters?

Escaping is essential when you’re working with file extensions, version numbers, and IP addresses — all of which contain literal dots. Without escaping, regex sees the dot as "any character" instead of an actual dot.

Take this example:

echo "file.txt" | grep '\.txt$'

The pattern \.txt$ tells regex to look for any string that ends with .txt, and the backslash (\.) ensures that the dot is treated as a literal dot, not "any character."

This is why the pattern only matches file.txt, not something like fileXtxt.

TL;DR

As you can see, Regex is incredibly handy. However, knowing how regex works is one thing, but actually using it in your Bash scripts is where things get exciting. This is where you go from "I understand it" to "I’m automating everything with it."

So let’s get into some examples in action.

How to use Regex in Bash scripts

Bash gives you several ways to work with regex. The most common options are:

  • The [[ ]] construct — Used to check if text matches a pattern inside a Bash script
  • Tools like grep, sed, and awk — Used to search, filter, and manipulate text in files (that we just talked about)

Each method has its place. If you’re working with filenames, variables, or user input, [[ ]] is your go-to. If you’re searching for specific lines or patterns in log files, that’s where grep, sed, or awk come in.

Let’s see how you can use each one in practical situations.

How to use [[ ]] to match patterns in variables

When working with filenames or user input in your scripts, you often need to validate if something fits a specific pattern.

This is where Bash’s [[ ]] construct shines. It’s lightweight and built right into Bash, making it ideal for quick checks like filtering file extensions or ensuring inputs follow a format.

For example

Say you’re writing a script that processes .log files in a folder, and you want to ignore everything else. Instead of looping through all filenames manually, you can use a simple pattern match to focus only on .log files.

Here’s how it works:

filename="error.log"
if [[ $filename =~ \.log$ ]]; then
  echo "This is a log file."
else
  echo "This is not a log file."
fi

The [[ $filename =~ \.log$ ]] checks if the filename ends in .log. If it does, the script runs the desired action. This approach saves you from writing tedious if-else conditions and makes your script more concise and readable.

How to use grep to search for patterns in files

Need to scan through logs or large text files for specific entries? That’s where grep comes in.

Think of it as "Ctrl + F" for your terminal, but far more powerful. Whether you’re pulling error messages from logs, filtering specific dates, or extracting IP addresses, grep does it all in seconds.

For example, suppose you want to find all instances of "error" in a system log:

grep 'error' system.log

This scans the file line by line, printing any that contain "error" - just like we talked about earlier. But what if you need to narrow it down further?

For instance, say you’re looking for log entries for a specific date like 2023-12-19. Well, instead of manually scanning, you can use a regex to match any date in the YYYY-MM-DD format:

grep '[0-9]{4}-[0-9]{2}-[0-9]{2}' system.log

grep is your best friend for searching logs, filtering CSV rows, or hunting for error messages in large files. It’s fast, intuitive, and invaluable for pinpointing exactly what you need.

How to use sed to find and replace patterns

What if you don’t just want to find a pattern but also replace it? Enter sed. It’s the tool of choice for modifying text in bulk, whether renaming files, cleaning up messy data, or anonymizing sensitive information.

Say you want to replace every occurrence of "error" with "issue" in a log file. Instead of editing line by line, you can do this in one command:

sed 's/error/issue/g' system.log

Here’s how it works:

  • s/error/issue/: This substitutes "error" with "issue"
  • The g flag ensures it replaces every match on a line, not just the first

sed simplifies repetitive edits, such as renaming multiple files, standardizing formats in CSVs, or hiding sensitive data like email addresses. It’s a must-have for bulk text processing.

How to use awk for advanced filtering and extraction

When you’re working with structured files like logs or CSVs, sometimes you don’t just need to find a pattern — you need to extract specific pieces of data. That’s where awk comes in.

Think of it as the Swiss Army knife of text processing, perfect for filtering columns or generating quick reports.

For example

Suppose you want to extract the first field (like an IP address) from a log file. With awk, it’s as simple as:

awk '{ print $1 }' access.log

Now let’s take it up a notch. If you’re extracting IP addresses from a log file, you can also use a regex pattern like this:

awk '/[0-9]{1,3}(\.[0-9]{1,3}){3}/ { print $1 }' access.log

awk is essential when working with structured data. Whether you’re analyzing logs, extracting specific fields, or creating summary reports, awk handles the heavy lifting so you can focus on the results.

Give Regex a try for yourself!

So as you can see, Bash regex isn’t just "nice-to-have" — it’s a must-have for efficiency and automation. It turns hours of manual searching, filtering, and file renaming into seconds of work.

By mastering a few essential patterns, you’ll be able to:

  • Filter log files for errors in seconds
  • Rename files in bulk without manual edits
  • Extract IPs, timestamps, and other key data from files

These aren’t just concepts — they’re tools you’ll use daily as a DevOps Engineer, SysAdmin, or anyone managing files and data.

Your next step?

Take one of the patterns from this guide (like capturing a date or renaming files) and put it to work. Try it in a Bash script, automate a small task, or run a grep search on a log file. You’ll be amazed at how fast you can get results.

P.S.

Don’t forget - if you want to fast-track your Bash knowledge and get as much hands-on practice as possible, then check out my complete BASH scripting course:

learn bash

You'll learn Shell Scripting fundamentals, master the command line, and get the practice and experience you need to go from beginner to being able to get hired as a DevOps Engineer, SysAdmin, or Network Engineer!

Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students, and working DevOps professionals.


I guarantee that it's is the most comprehensive and up-to-date online resource to learn Bash Scripting. Plus, we'll give you the exact steps you need to take to get hired as a SysAdmin, DevOps Engineer, or Network Engineer no matter what level of experience you have!

More from Zero To Mastery

How to Become a DevOps Engineer: Step-By-Step Guide preview
Popular
How to Become a DevOps Engineer: Step-By-Step Guide

With 400,000+ jobs available and $120,000+ / year salaries, now is the perfect time to become a DevOps Engineer! Here's your step-by-step guide (with all the resources you need to go from complete beginner to getting hired).

How To Use Bash If Statements (With Code Examples) preview
How To Use Bash If Statements (With Code Examples)

Looking to level up your Bash skills with logic? In this guide, I cover everything you need to know about if statements in Bash - with code examples!

Bash Interview Prep: 30 Essential Questions and Answers preview
Bash Interview Prep: 30 Essential Questions and Answers

Ace your Bash interview! Explore 30 key questions with code examples that cover everything from basics to advanced topics. Get interview-ready now.