# Patterns: Regular Expressions for grep

A regular expression describes the shape of the text you want instead of the exact words, so grep can find every phone number or every date in a file; this lesson builds patterns from . and \[ \], ^ and $, \* and, with grep -E, + and |.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Write a grep pattern with ., \[ \], ^, $ and \*, building it a piece at a time
- Use grep -E with + to match one or more of something, and with an or bar to match one of several words

## Key takeaways

- A regular expression describes the shape of text. . is any one character and [0-9] is any one digit.
- ^ means the start of the line and $ the end, so ^Action finds lines that begin with Action.
- * repeats the thing before it any number of times, even none, so .* is anything at all. It isn't the * of file names.
- With grep -E, + means one or more of the thing before it and | means or.
- Put patterns in single quotes, so the shell passes every symbol to grep unchanged.

You run a book club, and the sign-up sheet and a year of meeting notes are text files. You need everyone's phone number for a call list, but you don't know the numbers: that's why you're looking. `grep 555-0142` only finds a number you already have. What you do know is the *shape* of a phone number: three digits, something in between, four digits. A **regular expression**, or *regex* for short, is a way of writing that shape so grep can find every piece of text that has it. Data analysts use them to pull dates out of reports, and web developers use the same patterns to check that an email address looks right.

## Set up the club folder

Copy these lines into your terminal. They make `~/terminal-practice/club`, with the sign-up sheet and four meetings' notes, and are safe to run again:

```bash
mkdir -p ~/terminal-practice/club/notes && cd ~/terminal-practice/club
printf 'Maya Chen      555-0142   joined 2025-03-08\nDiego Ramos    555-0187   joined 2025-06-21\nSam Patel      ask Maya   joined 2026-01-10\nInes Duarte    555.0119   joined 2026-02-14\nLee Park       555 0163   joined 2026-04-02\nRosa Lind      555-0170   joined 2026-09-17\n' > members.txt
printf 'Riverside Book Club, 2026-09-03\nBook: The Lost Canal\nAction: Maya books the library room for 2026-09-17\nAction: Diego brings snacks\nWhat do we read next?\n' > notes/2026-09-03.txt
printf 'Riverside Book Club, 2026-09-17\nBook: Night Skies\nNew member: Rosa Lind, 555-0170\nAction: Rosa sends the reading list\nWho hosts in October?\n' > notes/2026-09-17.txt
printf 'Riverside Book Club, 2026-10-01\nBook: Salt and Sea\nReminder: every Action is due by the next meeting\nAction: Ines picks the next three books\nCan we meet at the cafe?\n' > notes/2026-10-01.txt
printf 'Riverside Book Club, 2026-11-05\nBook: Winter Stories\nDiego has a new number: 555-0193\nAction: Sam books the room for 2026-11-19\n' > notes/2026-11-05.txt
ls notes
```

```text
2026-09-03.txt  2026-09-17.txt  2026-10-01.txt  2026-11-05.txt
```

## One character at a time: . and \[ \]

Build a pattern a piece at a time, checking each piece. Start with what you know: every number begins `555`, then a dash.

```bash
grep '555-0' members.txt
```

```text
Maya Chen      555-0142   joined 2025-03-08
Diego Ramos    555-0187   joined 2025-06-21
Rosa Lind      555-0170   joined 2026-09-17
```

Three, but Ines wrote a dot and Lee a space. In a pattern, `.` (read "dot") means *any one character*, so `555.0` matches all three ways of writing it:

```bash
grep '555.0' members.txt
```

```text
Maya Chen      555-0142   joined 2025-03-08
Diego Ramos    555-0187   joined 2025-06-21
Ines Duarte    555.0119   joined 2026-02-14
Lee Park       555 0163   joined 2026-04-02
Rosa Lind      555-0170   joined 2026-09-17
```

Now the digits. `[0-9]` (read "any one of 0 to 9") matches one digit, the same brackets as in *Wildcards: \*, ? and [ ]*. Brackets around a list of characters make a **character class**: `[aeiou]` is any one vowel. Write one class for every digit you expect, and the pattern is the shape of any phone number:

```bash
grep -r '[0-9][0-9][0-9].[0-9][0-9][0-9][0-9]' notes
```

```text
notes/2026-09-17.txt:New member: Rosa Lind, 555-0170
notes/2026-11-05.txt:Diego has a new number: 555-0193
```

![The pattern drawn as eight boxes in a row, seven that each match any one digit and, in fourth place, a dot that matches any one character, above three phone numbers, 555-0142, 555.0119 and 555 0163: each digit box sits over one digit of every number, and the dot box sits over the dash, the dot and the space, so all three numbers fit the same shape](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/1384af1836e14c96b934b66238b45565.webp)

Two numbers from the notes that weren't on the sheet. The single quotes matter: inside them the shell changes nothing, so the brackets reach grep as you typed them, rather than being matched against file names first.

## Start and end: ^ and $

`^` (read "caret", or "the start") pins a pattern to the start of the line. The notes put each task on a line that begins `Action`, but the word appears elsewhere too. Compare `grep -r 'Action' notes` with this:

```bash
grep -r '^Action' notes
```

```text
notes/2026-09-17.txt:Action: Rosa sends the reading list
notes/2026-10-01.txt:Action: Ines picks the next three books
notes/2026-11-05.txt:Action: Sam books the room for 2026-11-19
notes/2026-09-03.txt:Action: Maya books the library room for 2026-09-17
notes/2026-09-03.txt:Action: Diego brings snacks
```

The reminder line, `every Action is due…`, is gone: there, Action isn't at the start. `$` (read "dollar", or "the end") pins a pattern to the end of the line, so `?$` finds every line that ends in a question mark, the questions still open:

```bash
grep -r '?$' notes
```

```text
notes/2026-09-17.txt:Who hosts in October?
notes/2026-10-01.txt:Can we meet at the cafe?
notes/2026-09-03.txt:What do we read next?
```

## Anything in between: \* and .\*

`*` (read "star") means *the thing right before me, any number of times, even none*. On its own after a letter it's rarely what you want; its everyday use is after a dot. `.*` is any character, any number of times: anything at all. So `^Action.*Maya` is a line that starts with Action and mentions Maya somewhere after it:

```bash
grep -r '^Action.*Maya' notes
```

```text
notes/2026-09-03.txt:Action: Maya books the library room for 2026-09-17
```

This is where patterns and wildcards part ways. In a file name, `*` alone means "anything". In a regular expression it only repeats what's before it, and "anything" is `.*`.

## More with -E: + and |

grep's **extended** patterns, turned on with `-E`, add two more symbols. `+` (read "one or more") repeats the thing before it at least once, so `[0-9]+` is a number of any length. That finds every date in the notes, however each part is written:

```bash
grep -rE '[0-9]+-[0-9]+-[0-9]+' notes
```

```text
notes/2026-09-17.txt:Riverside Book Club, 2026-09-17
notes/2026-10-01.txt:Riverside Book Club, 2026-10-01
notes/2026-11-05.txt:Riverside Book Club, 2026-11-05
notes/2026-11-05.txt:Action: Sam books the room for 2026-11-19
notes/2026-09-03.txt:Riverside Book Club, 2026-09-03
notes/2026-09-03.txt:Action: Maya books the library room for 2026-09-17
```

The phone numbers have only one dash, so they don't match. Without `-E`, grep reads `+` as an ordinary plus sign and finds nothing here.

`|` (read "or") matches either side, so one search can look for two names:

```bash
grep -E 'Rosa|Lee' members.txt
```

```text
Lee Park       555 0163   joined 2026-04-02
Rosa Lind      555-0170   joined 2026-09-17
```

Inside the quotes, `|` is grep's "or", not a pipe; that's one more reason to quote every pattern. A Mac's grep understands every pattern in this lesson, `-E` included.

## Check yourself

2 questions to try on the lesson page.

## So what

When you know what something looks like but not what it says, a pattern finds it: every phone number, every date, every line that starts a task.

## Key terms

- [Regular Expression in the Shell](https://www.ask-codey.com/glossary/regular-expression-in-the-shell)
- [grep](https://www.ask-codey.com/glossary/grep)
- [Character Class](https://www.ask-codey.com/glossary/character-class)

## Cheat sheets

- [Patterns: Regular Expressions for grep](https://www.ask-codey.com/cheat-sheets/bash-regular-expressions-cheat-sheet)

## Related

- [Finding Text: grep](https://www.ask-codey.com/lessons/bash-grep)
- [Wildcards: \*, ? and \[ \]](https://www.ask-codey.com/lessons/bash-wildcards)
- [Finding Files: find](https://www.ask-codey.com/lessons/bash-find)

Source: https://www.ask-codey.com/lessons/bash-regular-expressions
