OkayTeacher
EN English 한국어 日本語 中文 हिन्दी বাংলা اردو Español Português Français Deutsch Русский العربية Bahasa Indonesia Tiếng Việt ไทย Türkçe

How to enable notifications

Notifications are unavailable in this browser or on this address. Try a supported browser on the secure SONUB site.

If notifications are blocked, allow them in your browser settings.

  1. Open the site controls beside the address bar, or your browser’s site settings. Set Notifications for this site to Allow.
  2. In your device settings, allow notifications for your browser and check Do Not Disturb or Focus.
  3. Return to SONUB, reload the page, and use the bell to enable notifications.

On iPhone or iPad, add SONUB to your Home Screen, open it there, then allow notifications.

Shell programming

Learn shell programming (bash) with missions in a real Linux terminal running in your browser. Start at lesson 1: predict each command's result, then run it.

A child types at a keyboard in front of a dark terminal window with a blinking cursor; across a counter, a friendly computer clerk takes the typed slip and hands back an answer slip, with a folder cabinet behind it where a pin marks the current folder.
The basics of the basics — your starting point

AI does it all for you — so why learn the shell?

Ask and it really will do everything. But if you hand everything over without understanding any of it, you become tied to AI — and you start to believe that what AI did, you did yourself.

Imagine a manager who delegates an important task without understanding the work. Even if the employee does it well, the manager struggles to judge the result or continue after that person leaves. The same need for basic knowledge applies when you delegate to AI.

AI is the same. Only someone who knows the basics can understand what AI did, check that it is right, and carry on alone when things get stuck. To be the one who directs AI, you need the basics.

are the basics of the basics, and the starting point. Learning shell commands teaches you how to handle an and the foundations of management — and the very next step is running a server yourself.

Don't be afraid. You can learn easily by asking AI about any command you don't know. Type one line at a time yourself and keep studying step by step.

Before Python, start with shell basics

often comes to mind when learning , but and are the practical foundation for installing and running AI tools.

Shell is not the main language for building AI , but it predates Python and has long helped people manage files and everyday tasks.

No deep study needed: start with a few common commands and build skills you can use for a lifetime.

The language behind every tool

the language that puts AI to work

The is very useful for running , managing files and automating development. But also works through , apps, notebooks and , so not every AI user has to use a .

View curriculum
  • Practice instantly — no install
  • · compatible
  • Real Linux, safely in your browser
MY LEARNING JOURNEY Take your first step today
0 / 16 lessons complete 0%
16step roadmap
53practice missions
0commands run
STEP BY STEP

Master shell in 16 guided lessons

Learn the basics, then , , running other languages and — all the way to .

Beginner → Practical
Part 1 · LESSON 1–LESSON 4

Command basics

Open the , handle files, find and count text, and chain together. After these four lessons you can already use a terminal on your own.

  1. LESSON 01 Complete NEXT Meet the terminal Command syntax and finding your location 3 missions Start
  2. LESSON 02 Complete NEXT Files and folders Create, move, delete, and inspect content 3 missions Start
  3. LESSON 03 Complete NEXT Text-processing toolkit Find with grep; count with sort, uniq, and wc 2 missions Start
  4. LESSON 04 Complete NEXT Pipes and redirection Connect commands and send output to files 2 missions Start
Part 2 · LESSON 5–LESSON 9

Writing scripts

Store values (), decide and repeat (conditions and ), and give a bundle of a name () to build your own — safely.

  1. LESSON 05 Complete NEXT Variables and expansion Store values, reuse them, and calculate 2 missions Start
  2. LESSON 06 Complete NEXT Conditions and loops Decide with if and repeat with for 2 missions Start
  3. LESSON 07 Complete NEXT Functions and scripts Create your own commands 1 missions Start
  4. LESSON 08 Complete NEXT Practical scripting Build useful automation 1 missions Start
  5. LESSON 09 Complete NEXT Safe shell: quotes and traps Finish with defensive scripting habits 2 missions Start
Part 3 · LESSON 10–LESSON 13

Real-world tools

Install and , then put , , and to work from the .

  1. LESSON 10 Complete NEXT Node.js commands Run JavaScript with node, npm, and npx 3 missions Start
  2. LESSON 11 Complete NEXT Install developer tools Put Grok, Codex, Claude Code, and Firebase on your PATH 3 missions Start
  3. LESSON 12 Complete NEXT Run other languages from the shell Drive Python, awk, and sed straight from the terminal 5 missions Start
  4. LESSON 13 Complete NEXT Regular expressions Find, pick and change text by its "shape" 5 missions Start
Part 4 · LESSON 14–LESSON 14

All in one view

Gather the shape that every shares — slots, , , joining symbols — so you can read a command you have never seen. You may read this lesson first.

  1. LESSON 14 Complete NEXT The shape of a command One shape behind every command 3 missions Start
Part 5 · LESSON 15–LESSON 16

Advanced — a real Linux

The practice is a real computer. Prepare a blank yourself, then download and install from the internet — the same steps you will use on a real . Then learn the of everyday tools such as , and | .

  1. LESSON 15 Complete NEXT Disks, packages and the internet Partition, format and mount a disk, then download and install like on a real server 11 missions Start
  2. LESSON 16 Complete NEXT Command patterns Know one shape and you can use commands you have never seen 5 missions Start
WHY SHELL?

Learn it once. Use it everywhere.

Run AI-generated solutions yourself

When you ask to organize files or a task, it often responds with . Reading shell lets you verify that answer before you run it.

A foundation that works everywhere

Shell's has barely changed for decades, so it is a universal computer language found on , and . Learn it once and use it for a long time across macOS, Linux, servers, , and AI tools.

macOS users can follow along as-is

macOS uses by default, but most basic commands and here work the same way. zsh differs from bash in splitting and numbering. Learn the using bash: selects bash when a script is run directly. The bundled macOS bash 3.2 lacks features such as -A, so try those examples in this course’s practice .

HANDS-ON EXAMPLE

Build a web server in one shell script

In about 20 lines of you can run a real web that returns a page to the — a vivid reminder that is just "a promise to exchange one chunk of text."

server.sh
#!/bin/bash

PORT=8084
PIPE=simple-web-server-pipe

rm -f "$PIPE"
mkfifo "$PIPE"

response() {
    read -r method path _
    echo "[$(date)] $method $path" >&2
    index=$(cat index.html)
    printf 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n%s' \
      "$index"
}

while true; do
    response < "$PIPE" | nc -l "$PORT" > "$PIPE"
done
  1. mkfifo "$PIPE" Creates a . The request receives and the response we build both travel through this channel.
  2. nc -l "$PORT" nc (netcat) opens 8084 and waits for the browser to connect — a server in a single .
  3. response() Reads the (method and ), it, and returns as an HTTP response.
  4. while true; do … done After responding, the connection closes and the starts over to accept the next visitor, keeping the server alive.

Put an index.html in the same folder, run bash server.sh, then open :8084 in your browser. Not for production, but perfect for understanding how HTTP works.

LESSON 01 · HANDS-ON LAB

Meet the terminal

In plain words Your first chat with the computer in text: you type one line, it answers with one line.

5 learning goals 3 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 02 · HANDS-ON LAB

Files and folders

In plain words What you do with a mouse in a file manager (make, copy, move, delete), done by typing.

4 learning goals 3 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 03 · HANDS-ON LAB

Text-processing toolkit

In plain words A highlighter, a sorter and a counter for a very long text.

5 learning goals 2 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 04 · HANDS-ON LAB

Pipes and redirection

In plain words Like a conveyor belt: each 's result moves to the next, then into a box (a file).

4 learning goals 2 practice missions About 15 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 05 · HANDS-ON LAB

Variables and expansion

In plain words Like a labelled box: put a value in, then take it out later by its name.

4 learning goals 2 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 06 · HANDS-ON LAB

Conditions and loops

In plain words Like 'if it rains, take an umbrella' and 'climb ten steps': deciding and repeating.

4 learning goals 2 practice missions About 15 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 07 · HANDS-ON LAB

Functions and scripts

In plain words Like a recipe card for a job you do often: say its name and the whole job is done.

4 learning goals 1 practice missions About 15 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 08 · HANDS-ON LAB

Practical scripting

In plain words Like dominoes: the next step falls only if the one before it succeeded.

4 learning goals 1 practice missions About 15 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 09 · HANDS-ON LAB

Safe shell: quotes and traps

In plain words Like fastening a seat belt: and safety stop accidents before they happen.

4 learning goals 2 practice missions About 15 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 10 · HANDS-ON LAB

Node.js commands

In plain words Like an for : fetches tools, and runs .

4 learning goals 3 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 11 · HANDS-ON LAB

Install developer tools

In plain words Like moving into a new workshop and checking each one works.

4 learning goals 3 practice missions About 10 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 12 · HANDS-ON LAB

Run other languages from the shell

In plain words Like an interpreter: the hands work to , or and brings back the result.

6 learning goals 5 practice missions About 30 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 13 · HANDS-ON LAB

Regular expressions

In plain words Like a stamp of a shape such as 'three digits, a dash, four digits' that finds every match.

6 learning goals 5 practice missions About 30 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 14 · HANDS-ON LAB

The shape of a command

In plain words Like a form with named boxes — once you know the boxes, you can fill in any form.

4 learning goals 3 practice missions About 20 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 15 · HANDS-ON LAB

Disks, packages and the internet

In plain words Like setting up a brand-new computer: prepare the empty , then download and install your apps.

6 learning goals 11 practice missions About 40 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
LESSON 16 · HANDS-ON LAB

Command patterns

In plain words Like a LEGO manual — once you know the brick shapes, you snap any together in the same order.

5 learning goals 5 practice missions About 25 min
Start practicing Lyen, the AI teacher, answers in text and voice (sign-in needed).
  1. 1 Read one concept at a time
  2. 2 Press the example right below it, then Enter
  3. 3 Solve the missions in the terminal
  4. 4 Stuck? Ask AI
LEARNING GOALS

What you will accomplish

  1. 1

    Use the basic [] [] structure

  2. 2

    Check your location and move between folders

  3. 3

    Print text with

  4. 4

    Find help with man and

  5. 5

    Add when a command needs administrator rights

A child types at a keyboard in front of a dark terminal window with a blinking cursor; across a counter, a friendly computer clerk takes the typed slip and hands back an answer slip, with a folder cabinet behind it where a pin marks the current folder.
The is a counter: you hand over one command, the computer does it and hands back the result. tells you which folder you are standing in.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command

    Some need nothing after them — the name on its own is the whole command.

  2. command options target

    The shape behind almost every : what to do, how to do it, what to do it to.

  3. cd to

    takes one place to move into. Leave it empty and you land in your .

  4. echo text

    prints back the text you hand it, so wrap anything with spaces in .

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
to
Where it ends up — the new place, or the new name.
text
The text to print. Put quotes around it if it has spaces.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice terminal's input line, then press Enter to run it. You can edit it first.

01

Terminal and prompt

A is a window for typing . The is the that reads and runs them; this course uses . A such as $ or % means it is your turn to type. Enter a command after it and press Enter. This way of using a computer is called a (command-line interface).

In more detail The prompt often shows more than $: on many computers it looks like you@laptop:~$ — your user name, the computer's name, the folder you are in (~ means your ) and then $. While a command is running the prompt disappears until it finishes; that is how you know the shell is busy. If a command seems stuck, press to stop it and get the prompt back.

02

The three parts of a command

Most contain an action, , and a . For example, -l notes.txt means list files, show details, and use the notes.txt file. For a command you do not know, man followed by its name or the command with shows its manual (the practice opens the real manual too — press q to close it).

In more detail Options usually start with a dash. One dash takes short, one-letter options, and you can join them: ls -l -a is the same as ls -la. Two dashes take long, readable names such as ls --all. Arguments are the things the command works on — usually file or folder names — and many commands accept several at once: ls notes.txt photos. If a name contains a space, wrap it in so it stays one argument.

03

Know where you are

Your always matters. Use to see it, to move, and to inspect its contents. ~ means your and .. means the .

In more detail A is the address of a file. An absolute path starts at the top of the with / (for example /home/you/notes.txt), so it works from anywhere. A relative path starts from where you are now (photos/cat.jpg), so it only works in the right folder. cd on its own takes you straight home, and cd - jumps back to the folder you were in before. Press Tab while typing a name and the completes it for you () — fewer typing mistakes.

04

Opening a terminal on your own computer

On this page you can practise right away in the practice , with nothing to install. On a , open the Terminal app (search for Terminal in ). On , open from the Start menu; to use the same as this course, install and open . On most desktops, press +Alt+T.

In more detail On a Mac the standard is zsh, which understands almost every command in this course; type bash if you want exactly the same shell as here. In PowerShell a few names (, , ) work but behave differently and many others do not exist, which is why WSL is recommended on Windows. Wherever you are, the arrow keys bring back earlier commands: press ↑ to repeat or edit the last one instead of typing it again.

05

When a command needs root — sudo

Anything that changes the whole computer — installing a , or writing into a system folder such as — belongs to the administrator (), so the answers . Put in front and the same runs with those rights: sudo add tree. The practice asks for no password; on your own computer you type your login password. Use it only when a command really needs it.

In more detail sudo normally runs one command as administrator. Use it for tasks such as installing (apk add or install), changing /etc or , or . It does not prevent mistakes: check the command and first. For errors in your , inspect and permissions. Even install -g may not need sudo, depending on the installation location.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

ls -la ~/projects
  1. lsThe : list what is inside a folder.
  2. -lLong format: one item per line, with , , size and date.
  3. -aAll: also show , whose names start with a dot, such as .
  4. ~/projectsThe : the projects folder inside your (~). Without it, lists the folder you are in.

What you see

total 16
drwxr-xr-x   5 you  staff  160 Oct 17 09:12 .
drwxr-xr-x  20 you  staff  640 Oct 17 09:10 ..
-rw-r--r--   1 you  staff   18 Oct 17 09:11 .env
-rw-r--r--   1 you  staff  512 Oct 17 09:11 notes.txt
drwxr-xr-x   3 you  staff   96 Oct 17 09:12 site

Each line is one item. The first letter is d for a folder or - for a file; the r, w and x letters say who may read, write or run it. Then come the owner, the size in , the date of the last change and the name. . is the folder itself and .. is the folder above it; the first line (total) is space and can be ignored.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

This is the : | . The on its left prints something, and instead of landing on the screen it goes straight into the command on its right. Lesson 4 teaches it properly, but you will see it in every lesson from here on — so read it as "and now hand that over".

  1. ls|wc -l

    lists the files and -l counts the lines it printed, so what you get is "how many files".

  2. cat notes.txt|head -3

    prints the whole file and -3 keeps only the first three lines of it.

  3. echo "hello shell"|wc -c

    -c counts the sent by , including the final . Nothing is saved to a file.

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • and need spaces: use -la, not ls-la.
  • is . Use ls and , not LS or Cd.
  • means you lack . Check the first. For a task that needs administrator rights, such as the example, add before the command.
LEARNING GOALS

What you will accomplish

  1. 1

    Create folders and empty files

  2. 2

    Copy, move, rename, and delete files

  3. 3

    Read files with , , and

  4. 4

    Use safely

Folders drawn as a tree: the big folder at the trunk is the root, branches lead to more folders, leaves are files, one folder has a little house for home, and an explorer with a map follows a dotted path from the trunk to one glowing file.
Every file sits somewhere on one big tree. A such as /home/me/notes.txt is the way from to that leaf, and moves you from folder to folder.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. mkdir options target

    Make a folder. With -p it also makes the along the way.

  2. cp options target to

    Copying needs two slots: what to copy, then where it goes. Whole folders need -r.

  3. mv target to

    Moving and renaming are the same shape — a new folder moves it, a new name renames it.

  4. rm options target

    Delete what you name. There is no recycle bin, so -i asks you first.

What goes in each slot

options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
to
Where it ends up — the new place, or the new name.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its into the practice 's input line, then press Enter to run it. You can edit it first.

01

Create — mkdir and touch

creates folders and creates empty files. mkdir -p a/b/c conveniently creates every missing . Like most , they print nothing when they succeed, so run to see the result.

In more detail touch has a second job: if the file already exists, it leaves the contents alone and only updates its 'last changed' time, so running it twice is safe. mkdir, on the other hand, complains with 'File exists' if the folder is already there — unless you add -p, which quietly accepts existing folders. Names with spaces need (mkdir "my notes"); many people use - or _ instead of spaces to avoid that trouble.

02

Copy, move, rename — cp and mv

creates a copy and keeps the original. moves a file or renames it. For both, write the source first and the destination second. For example, mv memo.txt diary.txt renames memo.txt to diary.txt in the same folder.

In more detail When the last is an existing folder, cp and mv put the files inside it with the same names: cp a.txt b.txt / copies two files into backup. To copy a whole folder you must add -r (), otherwise cp refuses; mv moves folders without any . Renaming with mv is instant even for huge files, because only the name changes and nothing is copied.

03

Read content — cat, head, tail

prints a whole file, while and show only the beginning or end. deletes immediately without using the Trash, so use it carefully.

In more detail head and tail show 10 lines unless you say otherwise: head -n 3 shows three. tail -f follows a file and keeps printing new lines as they are added, which is how people watch a while a runs ( stops it). For long files use less: it shows one screen at a time, Space pages down, / searches and q quits. Because rm skips the trash, a good habit is to run with the same name or first, and only then rm.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

cp -r photos backup/photos-2026
  1. cpThe : copy.
  2. -r: copy the folder and everything inside it. Without -r, refuses to copy a folder.
  3. photosThe source: the folder to copy.
  4. backup/photos-2026The destination: the copy is created as photos-2026 inside . The backup folder must already exist.

What you see

Nothing at all — when a command like this works, it prints nothing.

When the copy works, cp prints nothing. Check with backup — photos-2026 should be there. If backup does not exist you get ''; create it first with -p backup and run the copy again.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. ls -l|head -3

    A long listing can run off the screen, so -3 shows only its first three lines.

  2. mkdir -p backup&&cp notes.txt backup/&&ls backup

    Each step only happens after the one before it worked — means "only after that succeeded", and the last one shows you the result.

  3. ls photos|wc -l

    Counts what is inside the photos folder without opening it.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • removes a whole folder without confirmation. Always inspect the first.
  • and can an existing target silently. Build the habit of using -i.
LEARNING GOALS

What you will accomplish

  1. 1

    Find lines with

  2. 2

    Use grep -i and -n

  3. 3

    Sort lines with

  4. 4

    Count with

  5. 5

    Find files and folders by name and type with find

A sheet of text lines passes through a sieve: only the lines with a highlighted word fall through, then they are lined up in order and counted with a tally counter.
grep keeps only the lines you want, sort lines them up, and wc counts them.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. grep options pattern where

    What to look for comes first, where to look comes last. That order never changes.

  2. sort options file

    Sort the lines of a file. -r turns the order around and -n sorts by number.

  3. wc options file

    Count a file’s contents: -l counts lines, -w words, and -c . A byte count can differ from a character count.

  4. find where condition action

    find reads like a sentence — where to search, what to match, what to do with each hit.

What goes in each slot

options
How to do it. Starts with - or --, can be left out, and you may use several.
where
Where to look — the file or folder the command searches through.
file
The file the command reads from or writes to.
pattern
The letters or the shape to match. Quote it so the shell leaves it alone.
condition
The test that decides — it comes out either true or false.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its into the practice 's input line, then press Enter to run it. You can edit it first.

01

grep — find text

word file prints only lines. Add -i for search, -n for line numbers, or -r for folder search.

In more detail grep reads a file line by line and prints every line that contains the anywhere — a search for also finds 'concatenate'. Add -w to match whole words only. Put the pattern in when it contains spaces or symbols (grep "error " app.log). With -r and a folder, grep writes the file name in front of each match, so you can see where every hit is.

02

Sort and count — sort, uniq, wc

puts lines in order. combines identical neighboring lines, so use sort first to bring scattered duplicates together. -l counts lines. In the next lesson, you will connect these with to make simple summaries.

In more detail sort compares text, so by default 10 comes before 9; use sort -n for numbers and sort -r to reverse the order. uniq only joins identical lines that are next to each other, which is why it almost always comes after sort. wc without prints three numbers — lines, words and — in that order; -l, -w and -c choose one.

03

find — find files and folders

finds lines inside files; find finds the files and folders themselves. It walks the starting folder and every folder below it, and prints the of each match on its own line. The skeleton is find where action.

  • where — the folder to start from. . is the and ~ is your ; if you leave it out, find uses .
  • condition — -name "*.txt" matches by name (keep the in ), -iname ignores case, -type f picks only files and -type d only folders. With several conditions, all of them must match.
  • action — if you write none, it is -print (show the path). -exec {} \; runs a command for every match, with the path in place of {}.

find . with no condition lists everything below the current folder. Each result is a path starting from the folder you gave, such as ./photos/cat.jpg, so you can hand it straight to another command.

In more detail find does not just look at names — it walks the whole folder tree from the top down. Run it on a big place such as / or your whole home folder and it takes a while, and every folder you may not read adds a line; add 2> at the end to throw those errors away. -maxdepth 1 keeps find in the current folder without going into . -name is ( and readme.md differ) and looks only at the name, not the path — use -path "*/src/*" to match the path. Paired with grep, it answers "which files contain that word?": find . -name "*.js" -exec grep -l "" {} +

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

grep -rin "todo" src/
  1. grepThe : find lines that contain a .
  2. -r: search every file in the folder and in all its .
  3. -iIgnore case: , Todo and todo all match.
  4. -nNumber: show the line number of each match.
  5. "todo"The pattern to look for. The are not needed for one word, but they are a good habit.
  6. src/Where to look: the src folder.

What you see

src/app.js:12:  // TODO: check the password length
src/utils/date.js:3:// todo: handle time zones

Each hit is written as file name : line number : the line itself. Open that file at that line to deal with it. When nothing matches, prints nothing at all — that is not an error.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. grep "echo" todo.sh|wc -l

    finds the lines and -l counts them, so you learn how many times it appears.

  2. sort notes.txt|head -3

    Sorts the lines first and keeps the top three — the first three in alphabetical order.

  3. find . -name "*.txt"|wc -l

    find prints one per hit, so counting the lines counts the files.

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • is . and shell are different words, so grep shell will not find a line that says Shell. Add -i, as in grep -i shell, to ignore case.
  • grep prints nothing when there is no match. Use grep -q when you only need a yes/no result.
  • If you drop the , as in find . -name *.txt, the shell turns *.txt into the file names of the (notes.txt) before find even starts. find then misses the .txt files in , and with two or more .txt files in the current folder it stops with " must precede expression". Always quote the after -name, as in "*.txt".
LEARNING GOALS

What you will accomplish

  1. 1

    Connect commands with |

  2. 2

    Save output with >

  3. 3

    output with >>

  4. 4

    Understand standard-output flow

Three machines joined by clear pipes: the first sends out a stream of paper slips, the second is a drum sieve that drops plain slips into a tray and lets only star slips through, the third stacks and counts them, and a last pipe pours the star slips through a funnel into a box.
A | hands one command's output to the next command, like slips moving from machine to machine. The arrow > at the end pours the final result into a file.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command | command

    The left 's output becomes the right command's input, with no file in between.

  2. command > file

    Send the output into a file instead of the screen. It replaces whatever was there.

  3. command >> file

    The same thing, but it adds to the end instead of replacing.

  4. command < file

    Feed a file in as the 's input — the arrow points the way the text flows.

  5. command 2> file

    The 2 means , so this keeps errors out of your results.

What goes in each slot

command
What to do — the name of the command. It always comes first.
file
The file the command reads from or writes to.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Pipes ( | )

commandA | commandB sends A’s output to B as input. In | txt, ls makes a list and grep txt shows only lines containing txt. Run the left-hand first to see its output, then add | and the next command.

In more detail Every command has a (where it reads) and a standard output (where it writes — normally the screen). A connects the output of the left command to the input of the right one, and both run at the same time, so flows through as soon as it is produced. Build pipelines step by step: run the first command, look at its output, then add | and the next command.

02

Redirection ( >, >>, < )

> a file, >> , and < uses a file as input. Use 2> to redirect errors separately.

In more detail The sets up redirection before the starts. That is why data.txt > data.txt destroys the file: > empties data.txt first, and then sort reads an empty file. Write to a new file instead (sort data.txt > sorted.txt). Numbers name the : 0 is input, 1 is normal output and 2 is errors, so 2> sends only errors to a file, and > (the same as 1>) only normal output.

03

Show and save at once — tee

saves its input to a file and also sends it to the screen or the next . Think of a T-shaped splitting the flow in two. | tee list.txt displays and saves the list. Use tee -a to to an existing file.

In more detail tee is also the usual way to write a file that needs administrator rights. "text" | tee /somefile works. But sudo echo "text" > /etc/somefile fails. That is because the redirection (>) is done by your own , not by sudo. Add > at the end when you only want the file and not the copy on the screen.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

ls ~/Downloads | grep pdf | wc -l
  1. ls ~/DownloadsList the Downloads folder. When its output goes into a , writes one name per line.
  2. |Pipe: send that list to the next instead of showing it.
  3. grep pdfKeep only the lines that contain pdf.
  4. |A second pipe: pass the remaining lines on.
  5. wc -lCount the lines that arrive.

What you see

7

Only the last command writes to the screen; the others pass their output along. The single number is how many file names contain pdf. To see the names themselves, remove the last part (| -l) and run it again.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. ls|sort -r|head -3

    Three in a row: list, turn the order around, keep the top three. Each one only does its own small job.

  2. cat notes.txt|wc -l>count.txt&&cat count.txt

    > parks the number in a file, and then prints that file so you can see it really landed.

  3. ls|grep txt|wc -l

    Narrow it down first, count second — the usual way to answer "how many of those?".

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
>
Writes the output into a file. Whatever was in that file is replaced.
WATCH OUT

Common beginner mistakes

  • > immediately replaces the file. Use >> when existing content must remain.
  • Do not mix up | and >. > does not search anything — it just saves the list into a new file named grep. Use | to hand output to another and > to save it to a file.
LEARNING GOALS

What you will accomplish

  1. 1

    Create and read

  2. 2

    Quote variables safely

  3. 3

    Calculate with

  4. 4

    Capture output with

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. name=value

    Put a value into a box you name. No spaces around the = sign.

  2. "$name"

    Take the value out with $, and keep the so spaces survive.

  3. $(command)

    Run a and use whatever it printed as a value.

  4. $((value))

    Two pairs of brackets mean arithmetic — the works out the sum.

What goes in each slot

command
What to do — the name of the command. It always comes first.
value
A plain value — a number, a word, or a piece of text.
name
A name you choose yourself, for a variable, a function, or a loop.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Create and read variables

Create a with name=value and read it with $name. Never put spaces around =: name=Lyen works, while name = Lyen does not.

In more detail Variable names may use only English letters, digits and _ (no other ), and cannot start with a digit. By habit, your own variables are lowercase and settings shared with other (, HOME) are UPPERCASE. A variable lives only in the where you made it: open a new and it is gone. To keep a value for every , put the line in (or ~/.zshrc on a ).

02

Protect values with double quotes

Use "$name" so values containing spaces stay together. This is one of the most important safe-shell habits.

In more detail still let $ work, so "$name" gives the value, but they keep spaces and stop * from turning into file names. When letters follow a directly, use braces to show where the name ends: "${name}s" prints the value plus s, while "$names" looks for a variable called names. Single quotes switch variables off completely ('$name' prints $name).

03

Arithmetic and command substitution

inserts a calculation result: $(( 3 + 4 )) becomes 7. inserts a ’s output. today=$(date) runs date and stores its output in today. Remember: double parentheses calculate; single parentheses here run a command.

In more detail Inside $(( )) you can write without $ ($(( count + 1 ))) and use +, -, *, / and % (the remainder) with parentheses. only counts in whole numbers, so $(( 7 / 2 )) is 3 — the decimal part is dropped. $( ) is different: it runs a command. Together they make lines such as files=$( | -l); "$(( files * 2 ))".

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

today=$(date +%F); cp notes.txt "notes-$today.txt"
  1. today=Create a called today. There must be no spaces around =.
  2. $(date +%F)Run date and put its answer here; +%F asks for the date in the form 2026-10-17.
  3. ;End of the first — then run the next one on the same line.
  4. cp notes.txtCopy notes.txt …
  5. "notes-$today.txt"… to this new name: $today is replaced by the date, and the keep it one name.

What you see

Nothing at all — when a command like this works, it prints nothing.

Nothing is printed when the copy works. now shows notes-2026-10-17.txt; run the same line tomorrow and you get a new file with tomorrow's date — a simple daily .

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. count=$(ls|wc -l)&&echo "files: $count"

    catches what the joined printed, so a whole chain can become one value.

  2. echo "$HOME"|wc -c

    The value inside a can be handed on exactly like any other output.

  3. first=$(head -1 notes.txt)&&echo "$first"

    Keeps the first line of the file in a name you chose, and then prints it.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • Spaces around = make the treat the name as a . Use foo=bar.
  • split at spaces. Make "$name" your default.
LEARNING GOALS

What you will accomplish

  1. 1

    with if / elif / else

  2. 2

    Compare , numbers, and files with

  3. 3

    Process lists with for

  4. 4

    Distinguish numeric and string comparisons

A conveyor belt carries file boxes one by one to a robot arm; at a fork in the belt, each box goes left or right depending on a check light.
for takes items one at a time, and if decides what happens to each one.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. if [[ condition ]]; then action; fi

    if opens and fi closes. The decides whether the action runs at all.

  2. for name in list; do action; done

    Each item of the list takes a turn in your name, and the action runs once per turn.

  3. while [[ condition ]]; do action; done

    Repeat while the stays true, so something inside has to change it.

  4. case value in pattern) action;; esac

    Compare one value against several . Each ends with ;; and esac closes them all.

What goes in each slot

pattern
The letters or the shape to match. Quote it so the shell leaves it alone.
value
A plain value — a number, a word, or a piece of text.
name
A name you choose yourself, for a variable, a function, or a loop.
list
The items to go through, one at a time.
condition
The test that decides — it comes out either true or false.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its into the practice 's input line, then press Enter to run it. You can edit it first.

01

The if statement

if checks a and runs the after then when it is true. Use elif to check another condition, and else for what to do when none match. Finish with fi. Read if condition; then ... fi as “if this is true, do this.”

In more detail The condition after if is really a command: if runs it and checks its , and 0 means true. That is why if -q milk notes.txt; then … works without any brackets. Indent the lines between then and fi so you can see where each ends, and remember the ; before then when you write everything on one line.

02

Comparisons with [[ ]]

Use == and != for ; for numbers use (equal), -gt (greater than), -lt (less than), -ge (greater than or equal) and -le (less than or equal); and use -f or -d to check files and folders.

In more detail Inside you can join tests with (and) and || (or), and ! means not: [[ -f notes.txt && ! -d ]]. Quote in comparisons when in doubt; with the older it is required, because an empty variable leaves a hole in the . For pure numbers reads more naturally: if (( score >= 80 )); then …

03

The for loop

for item in list; do ... done puts each list item into a . You can over or over files.

In more detail The list after in is expanded before the loop starts, so for f in *.txt runs once per text file with the name in $f. If no file matches, the itself is used once ($f becomes *.txt), so check with [[ -e $f ]] or switch on (lesson 9). Do not loop over $(); the pattern is safer when names contain spaces.

04

Repeat a fixed number of times with seq

1 20 produces the numbers 1 through 20. for i in $(seq 1 20); do ... done puts each number into i and repeats 20 times. Keep in in the “for in list”. For a fixed range, you can also use for i in {1..20}.

In more detail seq can also count in steps and backwards: seq 0 5 20 gives 0 5 10 15 20, and seq 10 -1 1 counts down. Brace ranges such as {1..20} cannot use variables ({1..$n} does not work). When the end comes from a variable, use $(seq 1 "$n") or the ((i=1; i<=n; i++)).

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

for f in *.txt; do echo "$f: $(wc -l < "$f") lines"; done
  1. for f in *.txtRepeat once for every .txt file in this folder; each time, f holds one file name.
  2. doStart of the to repeat.
  3. echo "$f: …"Print the file name followed by its line count.
  4. $(wc -l < "$f")Count the lines of that file. With < the file goes in as input, so prints only the number.
  5. doneEnd of the — go on with the next file.

What you see

notes.txt: 3 lines
todo.txt: 12 lines

One line per file. The loop runs as many times as there are .txt files. If there are none, it runs once with the text *.txt and wc reports an error — the in lesson 9 prevents this.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. for f in *.txt; do wc -l "$f"; done|sort -n

    Everything the prints comes out as one , so a single -n puts all of it in order.

  2. ls|grep -q txt&&echo "txt here"

    -q prints nothing and only reports success or failure, which is exactly what reads.

  3. if ls|grep -q todo; then echo "found"; fi

    A joined can be the of an if — what it hands over is success or failure, not text.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • In [[ $x > 7 ]], > compares text, not numbers. Use for numeric comparison.
  • Spaces are required inside [[ and ]].
LEARNING GOALS

What you will accomplish

  1. 1

    Define and call a

  2. 2

    Use function such as $1

  3. 3

    Explain

  4. 4

    Understand

A small machine with a name tag: a file goes in through the input slot, and the machine puts out the file plus a labelled backup copy.
A function is a named machine: give it an argument, and the same steps run every time.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. name() { action; }

    Give a bundle of a name. The braces hold the body, and is the first value handed in.

  2. name value

    Call your own exactly like any other — name first, values after.

  3. bash file

    Hand a to . This works even when the file has no execute .

  4. chmod +x file

    +x marks the file runnable, and you only have to do it once.

  5. ./file

    ./ means "the file right here", because the does not search the .

What goes in each slot

file
The file the command reads from or writes to.
value
A plain value — a number, a word, or a piece of text.
name
A name you choose yourself, for a variable, a function, or a loop.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its into the practice 's input line, then press Enter to run it. You can edit it first.

01

Define and call functions

name() { ; } turns a command sequence into a reusable name. Inside a , and $2 hold incoming arguments.

In more detail Define a function before calling it. Inside, $1 and $2 are the arguments passed to that function. Print a text result with and capture it with result=$(myfunc). return does not return text: return 0 success, while a nonzero value such as return 1 signals failure (range 0–255).

02

Script files and shebangs

A stores several in a file such as hello.sh. Its first line, , is called a shebang. It selects when you run the file directly as ./hello.sh, even if your current is zsh. You can also choose the explicitly with bash hello.sh.

In more detail The shebang must be the very first line and start exactly with #!. Many scripts use #!/usr/bin/env bash instead: finds bash wherever it is installed, which helps on systems where bash is not in /bin. The . ending is only a hint for people; the shebang, not the name, decides which program runs the file.

03

Permission and arguments

Run +x hello.sh before ./hello.sh. Read incoming values with , $2, , and $#.

In more detail ./ means 'the file in this folder'. The looks for only in the folders listed in , and the is not one of them, so hello.sh on its own gives ''. -l shows the : contains x, so the file can be run. $0 is the 's own name, and "$@" in passes every argument on unchanged, even those with spaces.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

backup() { cp "$1" "$1.bak" && echo "saved $1.bak"; }
backup notes.txt
  1. backup()Define a called .
  2. { … ; }Its body. The space after { and the ; before } are required.
  3. cp "$1" "$1.bak"Copy the first to the same name with .bak added.
  4. && echo …Print a message — only if the copy worked.
  5. backup notes.txtCall the function: inside it, $1 becomes notes.txt.

What you see

saved notes.txt.bak

The first line only defines the function and prints nothing. The second line runs it and prints the message. Call it with another file name and the same steps run on that file — that is the point of a function.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. bash hello.sh|wc -c

    Whatever a prints can be piped on, exactly like the output of any other .

  2. chmod +x hello.sh&&./hello.sh

    Mark it runnable and run it in one line — the second half only happens if the first one worked.

  3. bash todo.sh|head -1

    Only the first line of what the printed.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • Without +x, ./hello.sh may fail with . You can also run hello.sh.
  • Quote such as "$1" so spaces stay intact.
LEARNING GOALS

What you will accomplish

  1. 1

    Read success or failure through

  2. 2

    Chain success and failure with

  3. 3

    Process many files with for

  4. 4

    Understand scheduled execution with

Robot workers pass boxes along a conveyor; green traffic lights over the first two stations let the boxes move on, a red light over the third stops the line and a supervisor leans in to look.
Every ends with an exit code: 0 is a green light, anything else is red. A can check it and stop before a broken step ruins the next one.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command && command

    Run the second only if the first one succeeded.

  2. command || command

    Run the second only if the first one failed — your emergency exit.

  3. command >> file 2>&1

    Keep both the results and the in one .

  4. when command

    The shape: five time , then the . It runs on your own computer, not in this practice .

What goes in each slot

command
What to do — the name of the command. It always comes first.
file
The file the command reads from or writes to.
when
When to run it — five fields: minute, hour, day, month, weekday.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Exit codes and $?

Every returns 0 for success or another number for failure. reads the previous result, and A B runs B only when A succeeds.

In more detail Each chooses its own non-zero codes — , for example, returns 1 when nothing matched and 2 when something went wrong. A || B is the opposite of &&: B runs only if A failed, as in || "could not create". End a successful with exit 0 and a failed one with exit 1 (or another number), so whoever runs it — a person, or another script — can check the result.

02

Automate repeated work

Combine with file to process many items, such as copying every photo into a dated folder.

In more detail Before changing files, preview the command by putting before it. This is a . $(date +%F) in a backup name separates days, but another run on the same day reuses the name. Include the time for separate backups. Print messages at each step to show progress.

03

Scheduled runs — cron

schedules at set times. Open the configuration with crontab -e. Write five —minute, hour, day, month, weekday—then the command. 0 3 * * * means daily at 3 a.m. Put the schedule below in crontab; it is not a command to run at the .

In more detail The five fields are minute (0–59), hour (0–23), day of the month, month and day of the week (0 = Sunday). * means every; */15 in the minute field means every 15 minutes; 1-5 in the weekday field means Monday to Friday. cron runs with a very short and no , so use full and send the output to a , or you will never see the errors. crontab -l lists your current .

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

*/15 9-18 * * 1-5 /home/you/check.sh >> /home/you/check.log 2>&1
  1. */15Minute: every 15 minutes (0, 15, 30, 45).
  2. 9-18Hour: from 9 to 18 o'clock.
  3. * *Any day of the month, any month.
  4. 1-5Day of the week: Monday (1) to Friday (5).
  5. /home/you/check.shThe to run, with its full — starts in your , not in the script's folder.
  6. >> /home/you/check.logAdd its output to the end of a .
  7. 2>&1Send errors to the same log, so you can read them later.

What you see

Nothing at all — when a command like this works, it prints nothing.

This line is not typed at the : you save it with crontab -e and cron runs it by itself — from 9:00 to 18:45, every 15 minutes, on weekdays. Nothing appears in the ; open check.log to see what happened, and crontab -l to check the line was saved.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. mkdir -p logs&&ls>logs/list.txt&&cat logs/list.txt

    Three steps, each one only after the one before it succeeded — this is how a keeps itself out of trouble.

  2. bash todo.sh>out.txt2>&1&&cat out.txt

    sends the into the same file as the results, so one holds the whole story.

  3. cat missing.txt||echo "no such file"

    is the other side of &&: the message appears only because the first failed.

The symbols that join them

2>&1
Sends the error messages to the same place as the results.
||
Runs what follows only if this part failed.
&&
Runs what follows only if this part succeeded.
>
Writes the output into a file. Whatever was in that file is replaced.
WATCH OUT

Common beginner mistakes

  • If a critical fails, stop with command exit 1 instead of continuing with bad state.
  • Quote . starts in your with a very short , so use absolute paths for and commands.
LEARNING GOALS

What you will accomplish

  1. 1

    Explain double, single, and unquoted text

  2. 2

    Understand and

  3. 3

    Use

  4. 4

    Always quote

At a workbench a person is about to press a big red button wired to a paper shredder, but first looks at a preview screen with an eye icon and a ticked checklist; nearby a stack of paper slips is held together by two clamps.
Before a that deletes or , look first — list the files it would change. Quotes are the clamps that keep a name with spaces in one piece.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command "target"

    A name with a space is one only inside . Without them it looks like two.

  2. "$name"

    still let $ fetch the value, but they keep it in one piece.

  3. 'text'

    mean "exactly these characters" — even $ loses its power.

  4. command -- target

    nothing is read as an , which saves names that start with -.

What goes in each slot

command
What to do — the name of the command. It always comes first.
target
What to do it to — a file or folder name, or a path.
name
A name you choose yourself, for a variable, a function, or a loop.
text
The text to print. Put quotes around it if it has spaces.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Three kinds of quoting

change how a is read:

  • "$var" (double quotes) — expands the variable but keeps spaces.
  • '$var' (single quotes) — keeps everything literal.
  • unquoted $var — may split at spaces and turn characters like * into file names.

When in doubt, wrap it in double quotes.

In more detail A quick test makes the difference visible: with name="a b", $name prints a b (the spaces are squeezed) while echo "$name" prints a b. To put a single quote inside text, wrap it in double quotes ("it's"). Inside double quotes only a few characters stay special — $, `, " and \ — and a in front protects them.

02

Word splitting and globbing

first splits an on whitespace (), then replaces * and ? with file names (). If file contains my notes.txt, $file / can read it as two files. cp "$file" backup/ passes it as one name.

In more detail Splitting uses the characters in IFS — space, tab and by default. Globbing then replaces *, ? and with matching file names, so a variable holding * can suddenly turn into every file name in the folder. Both happen only to unquoted ; inside double quotes they never happen.

03

Defensive options

helps catch mistakes early in a . Each has a role.

  • -e stops on failure, with exceptions such as conditions tested by if.
  • -u reports an error when you use an unset .
  • pipefail makes a failure inside a affect its overall exit status.

These options do not prevent every mistake. Check with and verify the actual results too.

In more detail Put set -euo pipefail right after the so it covers the whole script. Install shellcheck with a ( install shellcheck, install shellcheck). It works inside many editors and underlines mistakes as you type. Its warnings have codes such as SC2086 (missing ), and each has a page that explains the fix.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

cp -- "$src" "$dest/"
  1. cpThe : copy.
  2. --: everything after -- is a name, even if it starts with -.
  3. "$src"The source, in : a name with spaces stays one .
  4. "$dest/"The destination folder, in quotes; the final / makes clear it must be a folder.

What you see

Nothing at all — when a command like this works, it prints nothing.

Nothing is printed when the copy works. Without the quotes, a name like 'my report.txt' would split into two arguments; without --, a name starting with - (such as -r) would be read as an . With both safeguards, each value arrives as exactly one name, whatever it contains.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. ls *.txt|wc -l

    The fills in the star first, so -l is counting real file names.

  2. wc -l "report 2024.txt"|head -1

    The keep the name with a space in it as one file; without them it would look like two.

  3. grep 'e' "report 2024.txt"|wc -l

    around the , double quotes around the name with a space — and then the count.

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • Most accidents begin with missing . Make “always quote ” a habit.
  • Use [[ $a = b $c = d ]] instead of mixing operators inside single brackets.
LEARNING GOALS

What you will accomplish

  1. 1

    Check the with node -v

  2. 2

    Run a JavaScript file with node app.js

  3. 3

    Install and run with npm

  4. 4

    Run a tool once with without a

A laptop terminal with a green hexagon engine; a delivery truck brings labelled packages into a project folder, next to a checklist sheet.
Node.js runs JavaScript in the , and npm delivers the packages listed in package.json.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. node file

    Hand a file to and it runs it.

  2. command options

    works on nearly every tool, so it is the quickest way to check an install.

  3. npm action

    takes a sub-command in the second slot — run, install, init.

  4. npm action options target

    Write the name after install. You can run it in the practice when it is connected to the internet.

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
file
The file the command reads from or writes to.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its into the practice terminal's input line, then press Enter to run it. You can edit it first.

01

What is Node.js?

is a (a ) that runs outside the — in terminals and . Tools for building web pages, command-line () tools, build , and many depend on it. After basics, Node is the next foundation for installing .

In more detail Node.js uses the same as (V8). But instead of showing web pages, it reads and writes files, opens connections and runs other programs. with an even number (22, 24 and so on) become (LTS) releases that get fixes for years, so they are the safe choice for learning. Install from nodejs.org, or with (see the advanced topics).

02

The node command

Use -v (or ) to check the . node app.js runs a file; node -e "(1+1)" runs a one-liner. Missing files or errors print an .

In more detail Typing node on its own opens the — an interactive (>) where each line of runs at once; .exit or leaves it. A can read its from : with node greet.js Mina, process.argv[2] is "Mina". console.log writes to , so fit into like any other (node report.js | total).

03

npm and package.json

installs and manages : bundles of reusable . A project’s package.json lists its name, , required packages (dependencies), and (). In that folder, npm install downloads packages and start runs the start script. runs a package’s command without a installation.

In more detail npm install creates two things. is the downloaded code — often very large; never edit it or it. lists the exact versions installed — you do commit it so everyone gets the same. npm ci installs exactly what the lock file says, which is what automatic builds use. A version like means 'this version or a newer compatible one'.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

npm install --save-dev prettier
  1. npmThe that comes with .
  2. installDownload a and add it to the project.
  3. --save-devRecord it under devDependencies: needed while developing, not when the app runs. -D is the short form.
  4. prettierThe package name — here , a tool that formats neatly.

What you see

added 1 package, and audited 2 packages in 2s
found 0 vulnerabilities

reports how many packages it added (a package can bring others with it) and runs a quick check (audit). package.json now lists prettier under devDependencies, and prettier --write . runs it on the project.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. node app.js|wc -l

    Whatever printed is just text, so the can count it like anything else.

  2. cat package.json|grep name

    Reads the file and keeps only the line you actually care about.

  3. node --version&&npm --version

    Two checks in one line; the second only runs if the first tool is really there.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • If or says "", is not installed yet. Install from nodejs.org or , then reopen the .
  • npm install -g often causes and issues. Prefer nvm or fix npm permissions and install without sudo.
LEARNING GOALS

What you will accomplish

  1. 1

    Explain why is a prerequisite for many tools

  2. 2

    Know the common install for , , , and

  3. 3

    Tell (-g) apart from one-off runs ()

  4. 4

    Verify installs with commands

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command options

    Ask a for its to prove it really is installed.

  2. command -v command

    Prints the when the exists, and nothing when it does not.

  3. command action options target

    Most install lines are this shape: , sub-command, , . Use it on your own computer.

  4. curl options value -o file

    Download from the and save to the file after -o. This also works in the practice with an internet connection.

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
file
The file the command reads from or writes to.
value
A plain value — a number, a word, or a piece of text.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Why install tools in the terminal?

Modern development is not only an editor. These run in the :

  • — , ,
  • CLIs —
  • — and others

The and from Lesson 10 are the starting point.

In more detail Most of these tools need an account: you sign in once in the (for example firebase login) and the tool remembers you. They are ordinary in your , so everything from this course — , , — works with them too. An AI coding agent can run for you, which makes understanding what a command does more important, not less.

02

Common installs (short summary)

Official methods can change — always prefer the vendor docs. Typical starts:

  • : -fsSL https://claude.ai/install.sh | (or install -g @anthropic-ai/claude-code)
  • : curl -fsSL https://chatgpt.com/codex/install.sh | (or npm install -g @openai/codex)
  • : the official installer, or npm install -g @xai-official/grok
  • : npm install -g firebase-tools

Then check the with , codex --version, firebase --version.

In more detail If a is missing just after installation, open a new first: it may pick up the updated . To reload settings manually, use the file for your , such as for zsh or ~/.bashrc for bash. Update using the installation method. For npm , use npm install -g firebase-tools to update, or npm uninstall -g followed by the name to remove.

03

Safe install habits

Prefer reading | from trusted official domains. If hits errors, fix instead of using . In teams and , pin as so stay consistent.

In more detail Download an installer and read it before you run it. Get it with curl -fsSL -o install.sh, read it with less install.sh, then run bash install.sh. In -fsSL, -f stops on errors, -s keeps quiet, -S still shows errors and -L follows . Never paste a curl | bash line from a random video or forum; copy it from the tool's official site.

In more detail

Read one line, piece by piece

A command people really use, taken apart. Read what each piece does, then what appears on the screen.

curl -fsSL https://claude.ai/install.sh -o install.sh
  1. curlDownload from a web address.
  2. -fsSLFour short together: fail on errors, stay quiet, but still show errors, and follow .
  3. https://claude.ai/install.shThe official address of the installer.
  4. -o install.shSave it to a file instead of running it at once.

What you see

Nothing at all — when a command like this works, it prints nothing.

Nothing is printed when the download works. Read the file with less install.sh, then run it with install.sh. With | bash instead of -o install.sh, the would run immediately without you seeing it.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. cat tools.md|grep -i git|head -3

    Read it, keep the lines about (-i ignores capitals), then show only three of them.

  2. which bash&&bash --version|head -1

    Find the first, and only then ask it for its .

  3. ls|sort|head -2

    The plainest chain there is — list, , cut.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • | is convenient but risky from unknown sources. Confirm the official first.
  • Similar names use different . Use exact names like @anthropic-ai/claude-code, @openai/codex, and .
LEARNING GOALS

What you will accomplish

  1. 1

    Run one-liners in other languages with -c and -e

  2. 2

    Save a to a file and run it with python3 file.py

  3. 3

    Pass a multi-line program to a with a (<<PY)

  4. 4

    Edit text across a file with sed

  5. 5

    Pick out and total them with awk

  6. 6

    Check AI-generated before you run it

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. awk '{action}' file

    runs your action on every line, where is the first .

  2. awk 'condition {action}' file

    Put a in front of the braces and only the lines get the action.

  3. sed 's/pattern/value/' file

    s stands for substitute — find this, put that. Read the slashes as .

  4. python3 file

    Hand a .py file to and it runs.

  5. python3 -c 'action'

    -c runs one line of right there, with no file at all.

What goes in each slot

file
The file the command reads from or writes to.
pattern
The letters or the shape to match. Quote it so the shell leaves it alone.
value
A plain value — a number, a word, or a piece of text.
condition
The test that decides — it comes out either true or false.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

The shell is the conductor, not the soloist

Doing every calculation in alone hits a wall fast. Instead the shell decides who to call and when: for math and , for tables, for bulk text edits — and the shell stitches the results together. Knowing the shell means you can use languages you have never studied.

02

Pass a single line — -c and -e

Most languages accept as an and run it immediately: -c "code", -e "code", -e, -e. Just replacing your calculator with python3 -c "(2**10)" makes the far more powerful.

03

Save it to a file and run it

If a is worth keeping, put it in a file. Write the into hello.py, and hello.py runs it as is.

The from Lesson 7 makes it simpler:

  • Write #!/usr/bin/env python3 as the first line.
  • Run +x hello.py to make it .
  • Now ./hello.py runs on its own, without python3 in front.

Type afterwards and you see whether it succeeded (0) or failed (1). So you can chain the next with , exactly as Lesson 8 showed.

04

Pass many lines — the heredoc

Use a to pass several lines of at once. Enter - <<'PY', write code on the following lines, and finish with PY alone on a line. The text between the markers goes to Python. The - means read code from instead of a file. also use this form for multiline code.

05

<<'PY' is not the same as <<PY

Quote the (<<'PY') and the body is passed through . Leave it unquoted (<<PY) and the first substitutes $variables and $() inside it. rarely contains $, but a or that does will break silently — so quoting the tag is the default when passing code.

06

sed — the stream editor that finds and replaces

The basic shape for replacing text with is s/find/replace/:

  • sed 's/find/replace/' — changes the first match on each line.
  • Add g at the end () to change every match on the line.
  • Put a line number or // in front to choose the lines (2s/…/…/, /error/s/…/…/).
  • Pair -n with p to print only the lines you picked.
  • The need not be /. For , s|/old/path|/new/path| reads much better.

To edit the file itself, write sed 's/find/replace/' file. It is safe because it keeps the original as file.bak. ( and differ here — see Watch out below.)

07

awk — a small programming language for tables

is a small language and for tabular text. It splits each line on whitespace: is the first , $2 the second. counts fields; NR counts lines. In awk ' { action }', the pattern selects lines and { } sets the action. Omit the pattern for every line, or the action to print lines. runs once before reading; END runs once afterward, for setup or totals.

08

Chain languages with pipes

that read and write standard output chain freely through . For example, sales.txt | '{print }' | -n | -3 does all this in one line:

  • read the file
  • take the second
  • sort numerically
  • keep the three largest

awk and are joined this way; takes its through -c or a instead. That is what it means for the to be the conductor.

09

Run AI-generated code safely

usually answers in exactly this form. Before pasting it, check three things: read the file it touches, look for or > that , and back up anything important first with original original.bak. Being able to read means being able to verify what the AI gave you.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. awk '{print $1}' sales.txt|sort|uniq -c

    takes the first , brings equal lines together, and -c counts each .

  2. python3 hello.py|wc -c

    prints and the counts — neither of them has to know anything about the other.

  3. sed 's/=/ /' config.txt|awk '{print $1}'

    turns the = into a space so that can read the name as the first .

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • Always wrap an in . With double quotes the treats as its own and replaces it with nothing: awk '{print $1}' works, awk "{print $1}" does not.
  • The of a heredoc must sit at the very start of the line, with no spaces, spelled exactly the same. If you need , use <<- and indent with tabs.
  • Quote the tag (<<'PY') when passing . Unquoted, any $ inside your code becomes a shell variable and breaks it silently.
  • rewrites the file with no undo. Run it without -i first, check the result on screen, then add it.
  • On , sed -i '' file without a name fails with an error (such as invalid code, depending on the file name) and leaves the file untouched — macOS requires a name after -i. Write it as sed -i.bak and the same command works on macOS and alike.
  • sed 's/a/b/' file > file empties the file before reading it, so the contents are lost. Write to a different name.
LEARNING GOALS

What you will accomplish

  1. 1

    Describe the shape of text with basic symbols such as . * + ? [ ] ^ $

  2. 2

    Find lines with -E and pull out only the matching part with -o

  3. 3

    Reorder and fix text with -E and ( )

  4. 4

    Explain basic versus , and why is not a digit in grep -E

  5. 5

    Check values with [[ value ]] in and with 's re

  6. 6

    Choose the that come built in on , and

A long ribbon of mixed shapes runs under a stencil card with a star and a circle cut out; the matching purple stars and circles light up, and a magnet lifts them in an arc into a basket while the grey shapes stay behind.
A regular expression is a stencil for text: grep lays it over every line and lifts out only the parts that fit the pattern.
Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. grep -E 'pattern' file

    -E turns on the fuller language, where | means or.

  2. grep -oE 'pattern' file

    -o prints only the part instead of the whole line.

  3. sed -E 's/pattern/value/g' file

    g replaces every match on a line, not just the first one.

  4. [[ value =~ pattern ]]

    tests a value against a right inside .

What goes in each slot

file
The file the command reads from or writes to.
pattern
The letters or the shape to match. Quote it so the shell leaves it alone.
value
A plain value — a number, a word, or a piece of text.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

A regular expression describes the "shape" of text

Searching for 010-1234-5678 finds that number. To find numbers with the same 3-4-4 digit format, write -[0-9]{4}-[0-9]{4}. [0-9] means one digit; {3} repeats it three times. This search rule is a . , , , , , , and share many basic symbols; check each tool’s details.

02

Symbols that pick one character

. is any one character (a.c matches abc and a1c). is one of the characters in the brackets, [^abc] is one character that is not in them, and [a-z] or [0-9] is one character in a range. Common sets have short names: is a digit, \w is a word character (letter, digit or _), and \s is whitespace. Capital letters (\D \W \S) mean the opposite. tools also accept names such as [[:digit:]] and [[:space:]].

03

Symbols that say how many times

* means the character just before it 0 or more times, + means 1 or more, ? means 0 or 1 (colou?r matches color and colour), means exactly n times, and {n,m} means n to m times. A repeat symbol applies only to the one character (or one bracket ) right before it. Repeats also grab as much as they can (they are ): ".*" takes everything from the first quote to the last quote on the line, so to stay inside one pair of write "[^"]*".

04

Symbols that mark a position — ^ $ \b

^ is the start of a line and $ is the end. ^error matches lines that start with error, done$ matches lines that end with done, and ^$ matches an empty line. is a word boundary, so \bart\b finds art but not party. These symbols take up no characters; they only mark a position. Do not confuse them with a ^ at the front of brackets ([^0-9]), which means "not" — something completely different.

05

Groups and alternatives — ( ) |

red|blue means red or blue. ( ) turns several characters into one unit — (ab)+ matches ab, abab and ababab. also let you take out what was found: \2 in , ${BASH_REMATCH[1]} in a , and m.group(1) in . That is why turning 2026-10-15 into 15.10.2026 takes just one line.

06

Using a symbol as a plain character — escaping

. * + ? ( ) [ ] { } ^ $ | \ all have special meanings. To find the character itself, put \ in front of it: a dot is \., a star is \*, an opening bracket is \(. If you write the 192.168.0.1 as it is, each . means "any character", so 192x168y0z1 matches too. And in the , always wrap the in . Otherwise the shell changes * ? $ { } its own way before the even sees them.

07

Basic and extended grammar — why add -E

You meet three grammars with and :

  • — what grep and sed read by default. + ? | ( ) { } are ordinary characters, so to use them as features you write \+ \| \( with a .
  • — add -E (grep -E, sed -E) and write the symbols as they are. When in doubt, always add -E.
  • Perl style () — uses symbols such as . So in grep -E, \d is the letter d, not a digit.

In tools write ; if you really want \d, use grep -P (). , , and (rg) understand \d out of the box.

08

Commands built in on macOS, Linux and Windows

and come with , , , find and . But each system is a little different:

  • macOS — grep and sed are . There is no grep -P, you write instead of , and needs '' after it when editing a file.
  • Linux — the GNU versions, where -P, \s and \+ all work.
  • — has , and has , -match and -replace. PowerShell understands \d as it is. Install or and you can use grep and sed too.

The " by OS" table below collects examples.

09

Using regex in shell scripts and Python

In a , check a value with [[ $value ]]:

  • It is true when it matches, so you can follow it with or use it in if.
  • The parts in ( ) go into ${BASH_REMATCH[1]}.
  • the pattern makes bash compare it as . So write the pattern without quotes, or keep it in a like re='…' and use [[ $value =~ $re ]].

In , import re and use the below. Write the pattern as a with an r in front, like r"+".

  • re.search — find
  • re.findall — find all
  • re.sub — replace
  • re.fullmatch — check the whole text
One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. grep -E "ERROR|WARN" app.log|wc -l

    Careful: the | inside the means "or" to , while the bare | is what joins the .

  2. grep -o "user=[a-z]*" app.log|sort|uniq -c

    Pull out only the parts, them, and count how often each one shows up.

  3. python3 regex.py|head -3

    Only the first three lines of what the printed.

The symbols that join them

|
Hands the output to the next command, with no file in between.
At a glance

Regex symbol table

Common symbols with their meaning, an example and text that the example matches. Click an example to drop it into the tester below and see it work.

Symbol Meaning Example Matches
. Any one character abc · a1c
* The character before, 0 or more times ac · abbc
+ The character before, 1 or more times abc · abbc
? The character before, 0 or 1 time color · colour
{n,m} Repeat n to m times 12 · 2026
^ Start of a line error: disk full
$ End of a line all done
[abc] One of the characters in the brackets a · e
[^abc] One character not in the brackets x · -
[a-z] One character in a range Q · k
| Or red · blue
( ) several characters into one ab · abab
\ Use a symbol as a plain character () .

Shorthand symbols

Symbol Same as Meaning Example
\d [0-9] One digit 2026
\w [A-Za-z0-9_] One word character (letter, digit or _) user_1
\s [ \t\n] One whitespace character (space, tab, newline) a b
\D \W \S [^0-9] … The opposite of each — not a digit, word character or whitespace abc
\b \< \> Word boundary — \bart\b finds art but skips party art
[[:space:]] \s POSIX name — a safe form that works in grep and sed everywhere, macOS included 42
BRE · ERE · PCRE

Basic and extended grammar

and read by default, so + ? | ( ) { } need a \ in front to work as features. Add -E for and write the symbols as they are.

Feature Basic — grep, sed Extended — grep -E, sed -E
1 or more times \+ +
0 or 1 time \? ?
Or a\|b a|b
Group \( \) ( )
Repeat n to m times \{n,m\} {n,m}

Ways to write symbols as they are grep -E sed -E awk [[ =~ ]] grep -P vim \v

\w \s are symbols. In grep -E, \d is the letter d, not a digit, so write . When you need \d, use grep -P (), or . With the grep and sed on , is safer than \s.

On your computer

Built-in commands by OS

you can use right away without installing anything. Pick your from the tabs.

  • grep -E Find lines that match a ( , so no -P) grep -E 'ERROR|WARN' app.log
  • sed -E Find and replace — when editing a file, -i needs '' after it sed -E -i '' 's/[[:space:]]+$//' notes.txt
  • awk Print only the you want from lines awk '/ERROR/ {print $1, $3}' app.log
  • find -E Find file names with a (-E for ) find -E . -regex '.*\.(jpg|png)'
  • perl replace with — installed out of the box perl -pe 's/(\d+)-(\d+)/$2-$1/g' a.txt
  • zsh [[ =~ ]] Check a value in , the default on [[ $v =~ ^[0-9]+$ ]] && echo number
  • python3 's re — the first run may ask you to install the python3 -c "import re; print(re.findall(r'\d+', 'a1b22'))"

On macOS the default shell is zsh and and are BSD versions. To work exactly like , install the GNU versions (ggrep, gsed) with install grep gnu-sed.

  • grep -E · -P · -o — with -P, only the part with -o grep -Po '\d{4}-\d{2}-\d{2}' app.log
  • sed -E -i Edit a file in place — GNU understands \s and \+ too sed -E -i 's/\s+$//' notes.txt
  • awk Put a on a (field) awk -F: '$1 ~ /^www/' /etc/passwd
  • find -regex Find file with a regex find . -regextype posix-extended -regex '.*\.(php|js)'
  • bash [[ =~ ]] Check a value and take ( ) out of [[ $s =~ ([0-9]+) ]] && echo "${BASH_REMATCH[1]}"
  • git grep Search quickly inside a only git grep -n -E 'TODO|FIXME'
  • python3 , included in most python3 -c "import re; print(re.sub(r'\s+', ' ', 'a b'))"

On grep and sed are GNU , so they behave exactly like the practice in this lesson.

  • findstr Search in — only part of (no + ? |) findstr /R /N "^ERROR" app.log
  • Select-String 's — find lines that match a Select-String -Path app.log -Pattern 'ERROR|WARN'
  • -match True when it matches; go into $Matches '2026-10-15' -match '^\d{4}-\d{2}-\d{2}$'
  • -replace Find and replace — understands and \s as they are (Get-Content a.txt) -replace '\s+$', '' | Set-Content a.txt
  • [regex]::Matches Find every match [regex]::Matches('a1b22', '\d+').Value
  • Where-Object Pick file names with a regex Get-ChildItem | Where-Object Name -match '\.(jpg|png)$'
  • WSL · Git Bash Install or to use as they are wsl grep -E 'ERROR|WARN' app.log
  • py Run with py once it is installed py -c "import re; print(re.findall(r'\d+', 'a1b22'))"

PowerShell uses regex, so \d \w \s and ( ) groups work as they are. in Command Prompt can do much less, so PowerShell is recommended.

At a glance

Practical patterns you will use often

to copy and use right away. Click one to load it into the tester and see where it matches.

Practice window

Regex tester

Change the and the parts of the text below light up at once. Feel free to edit the text too.

Matches and groups

This tester uses . tools do not use exactly the same . For example, means a digit here, but use with -E. Check the actual result when transferring a to grep -P too.

WATCH OUT

Common beginner mistakes

  • Always wrap the in . Written bare, as in -E , the expands {1,3} into 1 and 3 and turns * and ? into file names.
  • grep starts in , so the | in grep 'a|b' is just a character. Add grep -E to use or (|), + and ? as features.
  • is not a digit in grep -E or -E (it is the letter d). Use [0-9] in shell tools, or grep -P or if you really need \d.
  • . means "any character". To find a dot, write \.. Written as it is, 192.168.0.1 also matches 192x168y0z1.
  • Inside brackets in shell tools, \ is a plain character. [^\s] means "any character except \ and s", so for a non-space character write [^[:space:]] or [^ ].
  • .* grabs as much as it can. To match the text inside one pair of quotes, narrow it with "not that character", as in "[^"]*".
  • Putting quotes around the pattern, as in [[ $v "^[0-9]+$" ]], makes compare instead of a . Write it without quotes or keep it in a (re='…').
  • The sed and grep on are , so \s, \+ and grep -P may not work. With -E and the same works on both macOS and .
LEARNING GOALS

What you will accomplish

  1. 1

    Read any as slots: what to do, how, and to what

  2. 2

    Tell the four shapes of an apart: -l, --all, -la and -n 5

  3. 3

    Use a in the slot, knowing the fills it in first

  4. 4

    Work out the of a command you have never met with and man

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. command options target

    The one shape to remember: what to do, how to do it, what to do it to.

  2. command --help

    Almost every explains its own here. This is how you learn a command you have never met.

  3. command options target to

    takes the source, then the destination. Not every with two uses them this way; check each argument’s role.

  4. command pattern

    A * in the slot is filled in by the first, so the only ever sees real file names.

  5. command options | command options

    Each side keeps its own shape, and the simply passes the output along.

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
to
Where it ends up — the new place, or the new name.
pattern
The letters or the shape to match. Quote it so the shell leaves it alone.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Every command has three slots

Almost every line you type is , , — in that order. -la photos means "list (ls), with details and (-la), the photos folder". The command always comes first and the thing you act on comes last. Once you see the slots, a long line stops being frightening: you read it one slot at a time.

02

The four shapes of an option

control how a works. Short options start with one hyphen (-l), long ones with two (--all). For , -la combines -l -a. Some options take a value, such as -n 5 or --output report.txt. Start by reading “command options ”, then check the command’s help for required options and which can be combined.

03

The target slot and wildcards

are file or folder . such as and put the destination after the source. In filename , * matches zero or more characters, ? one, and one digit. The expands names before passing them to the command. is a separate that produces a and b whether or not those files exist.

04

Joining and grouping

join with symbols: | hands the output to the next command, > writes it into a file, >> adds to the end of a file, runs the next one only after success, || only after failure, and ; simply runs the next one. Grouping is only four marks: "..." keeps text in one piece, $(...) drops a command result in, $((...)) does arithmetic, and [[...]] tests a . open and close in pairs — if with fi, for with done, { with }.

05

Working out a command you have never met

You do not have to memorize . Type the name with , or man and the name, then read the first line: it shows the . In that line square brackets [ ] mean "you may leave this out", ... means "as many as you like", and the plain words are slots you fill in. Every command also reports success as 0, which you can read with $?.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. ls *.txt|wc -l

    slot, then the , then another with slots of its own — the same shape on both sides.

  2. ls --help|head -5

    A explains its in its first lines, so the help through .

  3. cat notes.txt report.txt|wc -l

    Two files in the slot become one , and the line count covers both of them.

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • belong before the . photos -la happens to work on but not everywhere, so keep to , options, target.
  • Quote a you do not want the to . In *.txt notes.txt the shell replaces *.txt first; grep "*.txt" notes.txt hands the star to grep itself.
  • A space is what separates one slot from the next, so a file name with a space looks like two targets. Wrap it in : -l "report 2024.txt".
LEARNING GOALS

What you will accomplish

  1. 1

    Find and with , and explain what a partition, a and a are

  2. 2

    Partition a blank disk with , with and attach it with

  3. 3

    Check free space with -h, write a file onto the new disk and unmount it safely

  4. 4

    Download from the internet with and

  5. 5

    Install with — for the system, in a for , for a project

  6. 6

    Run a ’s straight away with , without installing it

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. lsblk target

    With a , shows only that and the inside it.

  2. sudo mkfs.ext4 target

    The is a , not the whole — and everything on it is erased.

  3. sudo mount target to

    What to attach comes first and where to attach it second: the , then the folder.

  4. curl options value

    -s keeps quiet and -I asks only for the ; the value is the web address.

  5. command action name

    share one shape: the manager, what to do and the — add, install, install.

  6. npx options name text

    puts the name where a would go — such as --yes come first, and the words for the tool come after.

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
to
Where it ends up — the new place, or the new name.
value
A plain value — a number, a word, or a piece of text.
name
A name you choose yourself, for a variable, a function, or a loop.
text
The text to print. Put quotes around it if it has spaces.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

A disk is a blank notebook — look at it with lsblk

treats every as a file under . (list ) draws them as a tree:

  • is the first disk. Here it is an empty 128 practice disk.
  • sda1, sda2 … are the inside it. Right now there are none.
  • sr0 is a CD , and TYPE says disk, part or rom.

lsblk only looks and changes nothing, so it is always the first step. Every time you open this lesson or press , the disk is blank again — practise as often as you like.

02

Partitions — drawing lines in the notebook

A is a section of the that works like a of its own. The partition table at the very start of the disk records where each section begins and ends. writes it: mklabel makes a new, empty table, and mkpart primary 1MiB 100% adds one partition from 1 to the end of the disk. -s means "do not ask questions". Changing a disk is the administrator's job, so the starts with . Afterwards shows under sda.

03

Formatting — giving the partition a file system

A new is still raw space; it cannot hold files yet. Formatting writes a onto it — the catalogue that remembers every file's name, where its sits and who owns it. makes , the usual file system; mkfs.vfat makes , which and can also read (good for ). Formatting erases everything that was on that partition.

04

Mounting — attaching the disk to a folder

connects a to a folder instead of using or D:. This is ; the folder is the mount point. After / /, files written under /mnt/data go onto sda1. Check space with -h. The new belongs to : write with sudo or change with sudo you /mnt/data. Finish by unmounting with sudo umount /mnt/data.

05

curl — open a web address from the terminal

( ) is a web without a screen. When you type an address into a browser, the browser sends the a (an HTTP request) and the server sends back a — a ( means OK, means not found, and 302 mean it moved to another address, 500 means the server failed), (facts such as the kind and size of the content), and the body (, or a file). A browser draws the body as a page; curl shows it in the exactly as it arrived, or saves it to a file. That is why curl is used to check that a server is alive, to download files and to call . you will use most:

  • curl ADDRESS — print the body on the screen
  • -o FILE — save under a name you choose; -O — save under the name in the address
  • -L — follow the address it moved to (301 and 302)
  • -I — ask for the headers only (no body)
  • -s — hide the progress bar; -S — still show errors; -f — finish as a failure when the server answers with an error such as 404 or 500 ( 22)
  • -w "%{http_code}" — print the status code at the end

The -fsSL you often see in install is those four options together. Pass what curl received to other commands with a — | for the first lines, | for one of JSON. This talks to the real internet through the SONUB , and because it is an a download can take a few seconds.

06

git clone — copy a whole project

copies a project from a such as — every file and its whole . one file; git clone fetches the whole folder. takes only the , which is much faster. Go into the new folder with and look around with . This is an , so a download takes from a few seconds to a few tens of seconds.

07

Package managers — apk installs programs

A is an you use by typing. It finds a , downloads it together with everything it needs, and installs it. Each system has its own:

  • (this ):
  • and :
  • : (Homebrew)

apk add figlet installs a program, apk search finds one and sudo apk del removes it.

08

Python packages — pip inside a venv

installs from . First create a (venv) to keep them separate from the system Python. Run -m venv ~/venv, then source ~/venv/bin/activate. With (venv) in the , use pip install inside it; leave with deactivate. Creation takes about a minute here. Outside venv, this lab has no pip (); protected system environments may report .

09

Node packages — npm install in a project folder

installs per project. In an empty folder, npm init -y creates package.json, the project's list of what it needs. npm install ms downloads the package into and writes it into package.json, so anyone can rebuild the same setup later with a plain npm install. Each project keeps its own node_modules, so two projects never fight over .

10

npx — run without installing

comes with and runs the inside a without installing it. When you type npx cowsay hello, npx first looks for cowsay in the current project’s /.bin; if it is not there, it downloads the package from the into a (~/.npm/_npx) and runs it. Nothing is installed into the project or system-wide (-g), so it suits tools you need only once and tools that create a new project for you (such as npx create-vite). Common forms:

  • npx package — the first time it asks "Need to install the following packages: … Ok to proceed? (y)"; press y to go on
  • npx --yes package (-y) — goes on without asking; use it in
  • npx package@ — runs the version you choose (@latest is the newest)
  • npx -p package — for when the command name differs from the package name (cowthink inside the cowsay package)

The downloaded package stays in the cache, so it is not downloaded again next time. This is an , so the first download can take a few tens of seconds.

One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. lsblk|grep sda

    lists every and , and keeps only the lines about the practice disk .

  2. sudo mkdir -p /mnt/data&&sudo mount /dev/sda1 /mnt/data

    The is only if the folder was really made — means "only after that succeeded".

  3. curl -s https://sonub.com/robots.txt|head -3

    a file from the internet, and -3 shows only its first three lines.

The symbols that join them

&&
Runs what follows only if this part succeeded.
|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • erases everything on the you name. Here it is a blank practice , but on your own computer / may be the disk your system runs from — always check the name with first.
  • Without , , mkfs and stop with or . Changing disks is the administrator's job.
  • umount answers while you are still inside that folder. ~ first, then unmount.
  • Outside a , install fails — here with pip: , on with . Make a venv and switch it on instead of forcing it with --break-system-packages.
  • gets its answer from the itself here, so it does not prove that the internet works. Check with instead.
  • Everything you make here — partitions, files, installed — disappears when you reopen the lesson or press . Keep real work on your own computer.
  • downloads someone else’s and runs it at once. One wrong letter in the name can run a different — sometimes harmful — , so copy the name exactly from the official docs.
LEARNING GOALS

What you will accomplish

  1. 1

    Explain that most follow a , and that patterns make commands easier to learn, remember and adapt

  2. 2

    Grow one slot at a time — → → job and → -g → -s name

  3. 3

    Know that tools in one family use the same way — in and , -g means and -y means do not ask

  4. 4

    Work out the pattern of a new tool in the order "tool --help → --help"

  5. 5

    Read -fsSL URL | piece by piece, and check what a does before running it

Read the slots — do not memorize

The pattern of this lesson

Every keeps to a shape. Learn the shape once and you can read commands you have never seen before.

  1. npx name options

    One slot after the name — is the way to ask for usage that almost every tool understands.

  2. npx name action target options

    Add slots in order — job (add), (), (--list). Keep the slots in front as they are.

  3. command action options

    In the family, -g always means "" — install -g installs , -g lists what is global.

  4. command action --help

    Each takes different things, so put after the subcommand to see its own .

  5. curl -fsSL value | bash

    (), hand over (|), run () — change only the in the value slot and any installs with the same line.

What goes in each slot

command
What to do — the name of the command. It always comes first.
options
How to do it. Starts with - or --, can be left out, and you may use several.
target
What to do it to — a file or folder name, or a path.
value
A plain value — a number, a word, or a piece of text.
name
A name you choose yourself, for a variable, a function, or a loop.
action
The commands to run when it is their turn.
CORE CONCEPTS

Learn the core ideas

TRY IT Press an example to put its command into the practice 's input line, then press Enter to run it. You can edit it first.

01

Most commands follow a pattern

If you memorize every as a whole line, you will never finish. But most commands follow a set — what goes in each slot is always the same. Everyday nearly all take this shape: tool · job () · · .

  • add https://sonub.com -g — tool npx skills, job add, target the , option -g
  • install ms --save-dev — tool npm, job install, target ms, option --save-dev
  • https://github.com/karpathy/micrograd — tool git, job clone, target the URL

Knowing the pattern makes three things easy. Learning — a new command already has a shape you know, so you only check the slots. Remembering — instead of long lines you keep one shape and a few words. Adapting — change the values in the slots and you have a new command for your own job. If lesson 14, "The shape of a command", is the grammar of every command, this lesson is the pattern of the tools you use every day.

02

The npx pattern — one slot at a time

runs the of a straight away. Its shape is "npx command ". Grow it one slot at a time with , a that installs skills — keep what is in front and add one slot at the end:

  • — the tool alone. It shows a short list of its (add, list, remove…)
  • npx skills — the full manual of every subcommand and option
  • npx skills add https://sonub.com — installs the skills found at that (add is the job, the URL is the )
  • npx skills add https://sonub.com -g — for your whole computer (), not just this project folder
  • npx skills add https://sonub.com -g -s gp — only the gp skill

The line looks long, but it is the same shape with one slot added each time. So what you remember is not the line but the order — tool, job, target, then the options you need.

Read a long line the same way — npx skills add https://sonub.com -g -s infographics --agent claude-code -y:

  • npx — the tool that a package and runs its command straight away
  • skills — the tool to run (the skills CLI)
  • add — the job: install skills
  • https://sonub.com — the target: where the skills come from
  • -g — global: for every project on your computer, not just this one
  • -s infographics — the skill name: only infographics (-s is short for --skill)
  • --agent claude-code — the agent: only (short form -a claude-code)
  • -y — yes, don't ask: install all the way without questions

Put together it means "from sonub.com, install just the infographics skill into Claude Code for every project, without asking". Options such as --agent and -a have a long and a short name, and both are the same option. The options can come in any order and still mean the same.

03

One family, one meaning — -g is global

that handle , such as , , and yarn, are one family, so their mean the same thing. -g (--) means "global" — keep it for the whole computer, not just the current project folder. npm install -g lets you run firebase from any folder, add … -g shares a with every project, and npm -g shows what is . -y (--yes) means "do not ask, say yes". Some options work in almost every tool — (-h) for usage and for the number. But a short option can mean different things in different families. In npx skills, -s is the skill name (--skill), while in -s means quiet (--silent). When you use a new tool, check what its family means with --help.

04

Subcommands — small commands inside a big tool

Big such as , , and do many . So the second word, right after the tool name (the ), picks the job — npm install · · git · git · npx skills add · npx skills list. Each subcommand takes its own and , and works on a subcommand too: 1. tool --help — see which subcommands there are 2. tool subcommand --help — see that subcommand's target and options 3. Fill in the target slot, then add options one at a time

With these three steps you can work out the of a tool you have never seen.

05

The install line — curl -fsSL URL | bash

such as , Bun and uv install with almost the same one line:

  • -fsSL https://claude.ai/install.sh |
  • curl -fsSL https://bun.sh/install | bash
  • curl -LsSf https://astral.sh/uv/install.sh |

Read it piece by piece. curl an (a text of ) from the . -f stops with a failure instead of passing on an error page when the answers with an error (such as ), -s hides the progress bar, -S still shows errors, and -L follows a moved URL. | hands the fetched text to the next command, and bash (or sh) runs that text as commands. So the line means "fetch the at that URL and run it at once". Change only the URL and any tool installs with the same . In the practice , try SONUB's practice install script https://sonub.com/install/hello.sh — it makes just one command, hello.

06

Read before you run

… | runs someone else's from the internet on your computer straight away. It is handy, but the runs exactly as written, whatever it does. So keep to this order:

  • Use only the from the official documentation — one wrong letter means a different program
  • Read it first — send it to or less instead of bash, as in curl -fsSL URL | head -20, to see it without running it
  • If it is long, save it to a file, read it, then run it — curl -fsSL URL -o install.sh → less install.sh → bash install.sh
  • Do not add without a reason — running as administrator can change the whole computer
One command feeds the next

Joining commands

are hardly ever used alone. Put a symbol between two of them and what the left one prints goes straight into the right one.

  1. npx skills --help|head -5

    When the usage is long, -5 shows the first five lines — the 's is right at the top.

  2. npm ls -g|grep skills

    -g shows every , and keeps only the line with .

  3. curl -fsSL https://sonub.com/install/hello.sh|head -5

    Hand it to instead of and it is only read, not run — the habit before you use an install line.

The symbols that join them

|
Hands the output to the next command, with no file in between.
WATCH OUT

Common beginner mistakes

  • Do not swap the slots around. add -g works, but add looks for a called add — the order is , job, .
  • A short can mean different things in different families. -v is the , -v is "only lines that do not match", -v is "verbose". If you are not sure, check .
  • Copy the URL in curl … | from the official documentation. Running a from an unknown URL is like opening a a stranger gave you.
  • If a -g install on your own computer fails with a error (), do not just add . Install with and it works without sudo.
🚀 Advanced learning — go deeper

Read user input

Asks the user for a value and stores it in a . You don't have to the variable first.

echo "What's your name?"# Print the question on the screen
read name# Read the answer into the variable name
echo "Hello, $name!"# Use the value you got

read waits until the user presses Enter and stores what they typed. Add -p to show the question on the same line (read -p "Name? " name), -s to hide what is typed, as for a password, and -r so that are kept as they are. If you give no variable name, the answer goes into the $REPLY.

Current directory and subshells

Instead of the , the $PWD also tells you where you are. Commands wrapped in parentheses ( ) run in a separate , so a inside them does not change where you are outside.

echo "I am in $PWD"# Use the current through a
(cd /tmp; pwd)# Moves only inside the subshell → prints
pwd# Outside, you are still where you were

A subshell is a copy of the current . Changes made inside it — cd, new variables — vanish when it ends, so (cd build make) builds in another folder without moving you. The parts of a also run in subshells, which is why a variable set inside a while after a pipe is empty afterwards ( in lesson 4 solves this).

Clear the screen and find help

clear (or ) tidies up the screen. For a you don't know, there are three kinds of help: help for , man for the manual, and info for detailed documents.

clear# Empty the screen (same as Ctrl-L)
help for# Help for a built-in command such as for
man ls# The manual page of (press q to leave)

help only knows 's built-in commands (, for, read); for such as ls, use man. Inside man, move with the arrow keys or Space, search with / followed by a word, and quit with q. For a quick reminder, many commands print a short summary with , and type tells you whether a name is a built-in, a program or an (type cd, type ls).

Challenge

In a on your own computer, find out three things with three : which folder you are in, what is in your including , and whether is a of the .

Hint , then with an for hidden files and ~ as the , then type.

Show an example answer
pwd
ls -A ~
type cd

-A shows hidden files but leaves out . and .., so the list holds only real items. type cd answers 'cd is a shell builtin': cd is part of the shell itself, because a separate could not change the folder of the shell that started it.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Brace expansion

The expands a list or range inside { } into several names. For example, file.txt becomes five names, from file1.txt to file5.txt. Pass those names to or to create several folders or files at once.

mkdir -p project/{src,tests,docs}# Make three folders at once
touch file{1..5}.txt# Make five files, file1.txt to file5.txt
cp config.yaml{,.bak}# trick: config.yaml → config.yaml.bak

The shell expands braces before the runs, so the command never sees them — file{1..3}.txt prints file1.txt file2.txt file3.txt, which is a safe way to preview a . Ranges also work with letters ({a..e}). Do not put spaces inside the braces: {a, b} is not expanded.

Delete safely

cannot be undone. -i (ask about each file) and -v (show what was removed) cut down on mistakes, and sends files to the trash instead.

rm -i old.txt# Asks whether you really want to delete it first
rm -v *.tmp# Shows what it deleted
rm -r tempDir/# Deletes the folder and everything in it (careful!)

make rm dangerous: rm *.tmp is fine, but one extra space — rm * .tmp — deletes every file in the folder (only survive, because there is no -r). Run with the same first (echo *.tmp) to see exactly what would be removed. only removes empty folders, which makes it a safer choice than when you expect a folder to be empty.

More useful ls options

change how sorts and formats its list. These three are worth learning.

ls -l# One per line, with details
ls -t# Most recently changed first
ls -R# Into every too ()

Options combine: ls -lt shows the newest files first with details, ls -lS sorts by size, and -h prints sizes as K, M and G instead of (ls -lhS). ls -R can print thousands of lines in a big project; to see just the folders, find . -type d is easier to read.

Challenge

Create a folder called project with src, tests and docs inside, plus an empty in project. Copy README.md into docs, rename that copy to guide.md, and finally show the whole tree.

Hint Braces make the three folders in one line. Check each step with .

Show an example answer
mkdir -p project/{src,tests,docs}
touch project/README.md
cp project/README.md project/docs/
mv project/docs/README.md project/docs/guide.md
ls -R project

-p creates project and the three folders together. Copying into a folder keeps the name, so the copy is docs/README.md until renames it to guide.md. ls -R lists every folder and what is in it, so you can see the result in one go.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

More grep options

make far more powerful. Combine search, line numbers, exclusion and counting.

grep -r "TODO" ./src# -r: search a whole folder recursively
grep -n "error" app.log# -n: with line numbers
grep -v "debug" app.log# -v: only the lines without the word (the opposite)
grep -c "error" app.log# -c: only how many lines

Options can be combined: grep -rnw "total" . searches whole words in every file below the and shows line numbers. -l prints only the names of the files that match, handy before you open them. -A 2 and -B 2 show two lines after or before each match, showing what happened just around an error in a .

Edit streams with sed

reads a file line by line, replacing text or selecting lines to print. By default, the original stays unchanged. -i edits the file itself; use as in the example below to keep a .

sed 's/okay/great/g' file.txt# Every okay becomes great
sed '/^#/d' file.txt# Delete that start with #
sed -i.bak 's/old/new/g' file.txt# Edit the file itself + a .bak backup
sed -n '/error/p' log.txt# Print only the lines with error

sed reads one line at a time, applies its and prints the result; the file itself only changes with -i. In the g means every match on the line — without it only the first one is replaced. On a , sed -i needs a value for the backup ending (sed -i '' … for none), which differs from ; lesson 13 shows more of these differences.

Process columns with awk

splits each line on spaces (or the character given with -F) and handles the pieces as , $2 … It is strong at totals, filters and statistics.

echo 'a b c' | awk '{print $2}'# The second column → b
awk -F',' '{print $3}' data.csv# Split on commas, third column
awk -F',' '$2 > 25' people.csv# Lines whose second column is over 25
awk '{s+=$1} END{print s}' nums.txt# Total of the first column

awk runs its for every line: a in front ($2 > 25) chooses lines, and the part in braces says what to do with them. is the line number and NF the number of fields, so awk 'NR > 1' skips a header line. The runs once after the last line — ideal for totals and averages: awk '{s+=$1} {print s/NR}' prints the average of the first column.

cut, sort, uniq, and xargs

Joining small tools with gives you powerful processing. turns the output of one into the of the next.

cut -d ',' -f 1 file.csv# Only the first comma-separated
sort file.txt | uniq -c# Sort, then count the duplicates
find . -name "*.tmp" | xargs rm -f# Pass the files found to and delete them
find . -name "*.txt" -print0 | xargs -0 rm# Safe even for file names with spaces ()

A classic pipeline counts how often something appears: cut -d' ' -f1 access.log | | -c | sort -rn | shows the ten most frequent values of the first column. xargs builds a command from its input; add -n 1 to run it once per item, and use -0 together with find -print0 so that names with spaces arrive safely.

More find — search by size and date, run a command for each match

Besides the name, find can choose by size, last-modified date and depth, and run a for every match. With several conditions, all of them must match.

find . -type f -size +10M# Files larger than 10
find . -name "*.log" -mtime +30# last changed more than 30 days ago
find . -maxdepth 1 -type f# Only files in the , no
find . -name "*.tmp" -exec rm {} \;# Run for every file found (the goes in {})

In -size, + means "more than" and - means "less than", so -size +10M is any file larger than 10 MB (c is ; k, M and G are kilo-, mega- and gigabytes). -mtime -7 means changed within the last 7 days, -mtime +30 more than 30 days ago, and -mmin does the same in minutes. Between conditions there is an invisible "and" (-a); -o means "or" and ! means "not" — when you with parentheses, as in \( -name "*.jpg" -o -name "*.png" \), put a \ in front so the does not grab them. Ending -exec with \; runs the command once per match, while + collects many matches and runs it once, which is faster. -delete and -exec rm cannot be undone, so first run the same conditions without the action, check the list, and only then add it.

Challenge

names.txt has one name per line, and some names appear several times. Print each name once, with how many times it appears, the most frequent first.

Hint only joins lines that are next to each other — first. Then sort again, by number.

Show an example answer
sort names.txt | uniq -c | sort -rn

sort puts equal names next to each other, uniq -c joins them and writes the count in front, and sort -rn orders the lines by that number, biggest first (-n numbers, -r reverse).

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Here documents and here strings

<< hands several lines of text to a as its input in one piece. <<< is a light way to feed in one short line.

cat << EOF# The lines up to EOF become the input
several lines of text EOF# Everything between the two EOFs is the content
grep "banana" <<< "apple banana grape"# One line of input with <<<

The word after << is only a marker; EOF is a habit, not a rule. With <<'EOF' (marker in ) the text is taken literally — stays $HOME — which is what you want when writing a into a file. > config.txt << EOF … EOF is a quick way to create a small file of several lines.

Standard error and /dev/null

Output () and errors (stderr) travel on different channels. 2> takes only the errors, sends errors to the same place as the output, and /dev/null is the place where things are thrown away.

command 2> error.log# Only the go to a file
command > out.log 2>&1# Output and errors together into out.log
command > /dev/null 2>&1# Throw away both output and errors (quiet)

Order matters: > out.log 2>&1 sends both streams to the file, but command 2>&1 > out.log does not, because 2>&1 copies where normal output pointed at that moment — still the screen. In , &> out.log is a short form of > out.log 2>&1. Hiding noise you understand with /dev/null is fine, but hiding every error makes problems hard to find.

Process substitution

<() passes a command's output as if it were a file. It is handy for comparing the results of two commands directly, without .

diff <(ls dir1) <(ls dir2)# Compare two folder listings directly
while read l; do echo "$l"; done < <(ls)# set in the are still there after it

runs the command and gives its output a temporary file name such as /fd/63, so that only accept file names (diff, , paste) can read it. It is a feature of and zsh, not of plain , so the must start with . The second example matters because a while loop after a runs in a ; with < <(…) the loop runs in your and its variables survive.

Command substitution

$() drops a command's result into a sentence like a value. Prefer to (`) — it can be .

echo "There are $(ls | wc -l) files here"# Put the number of files into a sentence
today=$(date +%Y-%m-%d)# Today's date into a

$( ) runs the command first and puts its output in its place, without the final . Quote it — "$()" — when the result may contain spaces. It can be nested without , as in $( "$()"), which is the main reason it replaced backticks.

Challenge

Save the list of files in the to files.txt while also seeing it on the screen. Then add a line 'checked' at the end of files.txt, and print only the number of lines files.txt now has.

Hint shows and saves at once, >> adds to the end, and < gives the file as input.

Show an example answer
ls | tee files.txt
echo "checked" >> files.txt
wc -l < files.txt

tee writes the list to the screen and to files.txt at the same time. >> instead of . wc -l < files.txt reads the file as , so wc does not know a file name and prints only the number.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Parameter expansion

Inside you can take a value out and cut or replace part of it at the same time. It is the most powerful tool for reshaping a 's value.

echo "${var/Some/A}"# Replace the first Some with A
echo "${var//Some/A}"# // replaces every Some with A
echo "${var:0:7}"# Only 7 characters from position 0 (a )
echo "${#var}"# Length of the string

${var/old/new} changes the value only for this one use; the variable itself stays the same unless you assign the result back (var="${var//-/_}"). The are patterns, not : * means any text. These happen inside with no extra , so they are much faster than calling in a .

Defaults and letter case

You can give a default when a is empty, or change upper and lower case in one go.

echo "${foo:-default}"# Use "default" when foo is empty
: "${NAME:=guest}"# Set NAME to "guest" if it is missing, and keep it
echo "${var^^}"# All upper case ( 4+)
echo "${var,,}"# All lower case

only fills in a value for that moment, := also stores it in the variable, and :? stops the with your message when the value is missing (lesson 8). The colon matters: without it (${foo-x}) the default is used only when the variable does not exist, not when it is empty. ^^ and ,, need bash 4; the bash that comes with is 3.2, so test there before relying on them.

Trim paths

${var#pattern} removes a part from the start; ${var%} removes it from the end. One # or % removes the shortest match; ## or %% removes the longest. Try extracting the file name and folder in the examples below.

path="/usr/local/bin/run.sh"# An example path
echo "${path##*/}"# Remove up to the last / → run.sh ()
echo "${path%/*}"# Remove from the last / on → (dirname)
echo "${path%.sh}"# Remove only the .

Think of # as cutting from the left and % from the right (on the keyboard # sits left of %). One sign cuts the shortest match; two signs cut the longest. With file=archive, ${file%.*} gives archive.tar and ${file%%.*} gives archive. basename and dirname do the same job but start a separate each time.

Arrays

Put several values in parentheses; take one out with [number] and all of them with [@]. ${#array[@]} counts them.

array=(apple banana grape)# Make an of three
echo "${array[0]}"# The first one → apple (numbers start at 0)
echo "${array[@]}"# All of them → apple banana grape
echo "${#array[@]}"# How many → 3

Always write "${array[@]}" with the in a : each item stays whole even if it contains spaces. Add an item with array+=(cherry), and list the index numbers with ${!array[@]}. Arrays are a feature — plain has none, and zsh counts from 1 — so the same can behave differently there.

Associative arrays

An made with -A stores "name → value" pairs (like a dictionary or map in other languages).

declare -A person# Declare an associative array
person[name]="Alice"# Store a value under the label name
echo "${person[name]}"# Take it out → Alice
for k in "${!person[@]}"; do echo "$k"; done# Go through every key

declare -A is required: without it treats person as a normal array and every name silently becomes 0. The order of keys in a is not guaranteed, so the keys when order matters. Associative arrays need bash 4 or later, so they are missing from the bash that comes with .

Built-in variables and arithmetic

fills in some for you. Arithmetic goes inside , including powers and remainders. For decimals, use .

echo "$?"# of the just before (0 = success)
echo "$RANDOM"# A random number from 0 to 32767
echo "$(( 2 ** 10 ))"# A power → 1024
echo "scale=2; 10/3" | bc# Decimal math → 3.33

$? changes after every command, so save it at once (status=$?) if you need it later. Other useful ones: $$ is the 's process number, $0 the 's name, $SECONDS how long the shell has been running and $LINENO the current line. $RANDOM is fine for games, but not for passwords — use a proper generator such as rand for anything secret.

Formatted output with printf

behaves a little differently from system to system. works the same everywhere and controls width, alignment and decimal places exactly.

printf "Hello, %s!\n" "world"# Puts the value where is → Hello, world!
printf "%.2f\n" 3.14159# Two decimal places → 3.14
printf "%05d\n" 42# Five digits padded with zeros → 00042

printf does not add a by itself, so end the format with \n. If you give more values than placeholders, the format repeats: printf "%s\n" a b c prints three lines — a neat way to print a list. %-10s left-aligns text in a 10 characters wide, which lines up simple tables.

Challenge

The file holds report.final.pdf. Using only , print the name without its , the extension on its own, and the whole name in capital letters.

Hint % cuts from the right, ## cuts from the left as much as possible, ^^ makes capitals.

Show an example answer
file="report.final.pdf"
echo "${file%.*}"
echo "${file##*.}"
echo "${file^^}"

${file%.*} removes the shortest '.something' from the right → report.final. ${file##*.} removes everything up to the last dot → pdf. ${file^^} turns every letter into a capital ( 4 or later).

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Clean branching with case

When you compare one value with many , case is tidier than a chain of ifs (it is like switch in other languages).

case "$x" in# on the value of x
0) echo "zero";;# When it is 0
1) echo "one";;# When it is 1
*) echo "something else";; esac# * matches everything else

Each pattern can list alternatives with |, as in yes|y|Y), and use the same as file names: *.jpg|*.png) matches image names. The first pattern wins and the rest are skipped, so put the catch-all *) last. ;; ends each choice; forgetting it is the most common mistake with case.

for, while, and until

for goes through a list or a range, while repeats as long as a is true, and until repeats as long as it is false. There is a as well.

for ((i=1; i<=3; i++)); do echo "$i"; done# C-style for
while (( count < 3 )); do ((count++)); done# While the condition is true
until (( count >= 3 )); do ((count++)); done# While the condition is false

while is the natural choice for reading input line by line (while read -r line; do … done < file) and for waiting until something is ready. break leaves a early and continue jumps to the next round. An endless while true loop needs a way out — break, or when you run it by hand.

Regex matching and file tests

checks whether a fits a . -e, -f, -d and friends check whether a file or folder exists and what kind it is.

if [[ "$email" =~ ^[a-z]+@[a-z]+\. ]]; then echo match; fi# Regular expression match
if [[ -f "$file" ]]; then echo "file exists"; fi# -f: is it a regular file?
if [[ -d "$dir" ]]; then echo "folder exists"; fi# -d: is it a folder?
if [[ -z "$name" ]]; then echo "empty"; fi# -z: is it empty?

Write the pattern on the right of =~ without ; quoting it turns it into . The parts in parentheses are saved in the , so you can pull out pieces: after [[ $d =~ (+)-([0-9]+) ]], ${BASH_REMATCH[1]} is the first number. More : -s (not empty), -r, -w and -x (readable, writable, runnable) and -nt (newer than).

Challenge

Write a over the numbers 1 to 15 that prints Fizz for multiples of 3, Buzz for multiples of 5, for multiples of both, and the number itself otherwise.

Hint % gives the remainder of a division; a remainder of 0 means 'divisible'. Think about which test must come first.

Show an example answer
for i in {1..15}; do
  if (( i % 15 == 0 )); then echo "FizzBuzz"
  elif (( i % 3 == 0 )); then echo "Fizz"
  elif (( i % 5 == 0 )); then echo "Buzz"
  else echo "$i"
  fi
done

(( i % 3 == 0 )) is true when i divides by 3. The test for 15 comes first, because if/elif stops at the first test that is true — otherwise 15 would stop at Fizz. Every other number reaches else and is printed as it is.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Variable scope

makes a that is used only inside a , and makes a that cannot change. declare sets properties such as integer (-i), lower case (-l) and upper case (-u).

greet() { local msg="Hello"; echo "$msg $1"; }# msg is not visible outside the function
readonly PI=3.14# A constant that cannot be changed
declare -i num=42; num+=10# Integer type → the math happens by itself (52)
declare -u up="hi"; echo "$up"# Always upper case → HI

Without local, every variable you set inside a function is and can a variable of the same name elsewhere in the — a that is hard to find. Make local your default inside functions. readonly protects values that must never change; trying to change one prints an error. declare -p name shows how a variable is defined, which helps when .

Parse options with getopts

The standard tool for letting a take such as -v or -f filename. A colon (:) after a letter means "this option needs a value".

while getopts ":vf:" opt; do# v takes no value, f needs one
case $opt in# for each option
v) verbose=true ;;# With -v, turn verbose on
f) file="$OPTARG" ;; esac; done# The value of -f is in

The leading : in ":vf:" switches on quiet error handling: an unknown option gives opt the value ?, and a missing value gives :, so you can print your own help message. only handles short options (-v); long ones (--verbose) must be read by hand. After the , shift $((OPTIND - 1)) removes the options, so is the first real argument.

source and export

source (or .) runs another file in the current to load its and settings. passes a on to as well.

source ./lib/helpers.sh# Bring another file's functions and variables into the current shell
. ~/.bashrc# . is the short form of source
export API_KEY="abc123"# Let child processes see it too

Running a normally starts a new shell, so its variables and disappear when it ends; source runs it in your current shell, which is how you reload after editing it. export marks a variable so that started from this shell receive it — never the other way round: a child cannot change its parent's variables. Keep secrets such as in a file that is not shared or committed to .

Challenge

Write a greet that says hello to every name it receives, keeps a name with a space ("Mina Kim") together, and prints 'Usage: greet NAME...' and reports failure when it gets no name at all.

Hint is the number of , return 1 reports failure, and "$@" in keeps each argument whole.

Show an example answer
greet() {
  if (( $# == 0 )); then echo "Usage: greet NAME..."; return 1; fi
  local name
  for name in "$@"; do echo "Hello, $name!"; done
}
greet Leo "Mina Kim"

(( $# == 0 )) catches the call without names. keeps name inside the function. "$@" passes Leo and Mina Kim as two arguments, so the function prints two lines — without the quotes it would print three.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Cleanup with trap

trap sets a to run when the receives a (exit, error, interrupt). It is especially useful for cleaning up .

TMP=$(mktemp)# Make a safe temporary file
cleanup() { rm -f "$TMP"; }# A clean-up
trap cleanup EXIT# Always clean up when the script ends
trap 'echo stopped; exit 1' SIGINT# Safe even when is pressed

EXIT is a signal-like event raises whenever the script ends — normally, through exit, or because set -e stopped it — so one trap covers every way out. ERR runs whenever a command fails, useful for printing the line number (). creates a file with a unique name, so two copies of the script running at once do not each other's temporary files.

Defensive set options

set at the top of a make it tougher against errors. Use -e (stop on error), -u (no unset ) and -o (notice failures) together. -x is for .

set -euo pipefail# The standard first line of a sturdy script
set -x# Print each as it runs (debugging)
set +x# Turn debugging off

set -e has exceptions: it does not stop for a failure inside an if , in a or || list, or in a command whose result you test, because you are already handling it. A command that may fail harmlessly can be allowed with || true. Switching set -x on only around the part you are debugging keeps the trace short.

Background jobs and wait

Put & after a to run it in the background. lists them, and wait waits until they finish.

sleep 5 &# Run in the background
jobs# List of the still running
wait# Wait until every background job has finished
time sleep 1# Measure how long a command takes

& gives you the back at once and prints a job number and . brings a background job back to the front, pauses the running job and bg resumes it in the background. Background jobs usually stop when you close the ; command & or a tool such as keeps them running after you out.

Reusable safety patterns

Defensive that come up again and again in real : check required values, check that a exists, and stop right away on failure.

: "${VAR:?VAR is required}"# Stop with an error if VAR is missing
command -v jq && echo "jq found"# Check whether a command is installed
cp a b || { echo failed; exit 1; }# On failure, print a message and stop

is the portable way to check that a tool exists; send its output away when you only want the answer (command -v > { "please install jq"; exit 1; }). Put such checks at the top, so the script fails at once with a clear message instead of halfway through a job. Wrap several commands in { …; }; the spaces and the final ; are required.

Challenge

Write backup.sh: it copies every .txt file in the into a folder named backup-YYYY-MM-DD (today's date), stops with an if a copy fails, skips quietly when there are no .txt files, and finally prints how many files it copied.

Hint at the top, date +%F for the name, a for , and { …; exit 1; } after .

Show an example answer
#!/bin/bash
set -euo pipefail
dest="backup-$(date +%F)"
mkdir -p "$dest"
count=0
for f in *.txt; do
  [[ -e "$f" ]] || continue
  cp "$f" "$dest/" || { echo "copy failed: $f" >&2; exit 1; }
  count=$(( count + 1 ))
done
echo "copied $count file(s) to $dest"

date +%F gives one folder per day, and -p does not complain if it already exists. [[ -e "$f" ]] || continue skips the loop when *.txt matched nothing. sends the error message to the error . count=$(( count + 1 )) is used instead of ((count++)). That is because ((count++)) returns 'failure' when count is 0, which would stop a set -e .

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Understand each set option

combines three . Once you know which accident each one prevents, you see why it became the standard.

set -e# Stop at once if any fails
set -u# Using an undefined is an error (catches typos)
set -o pipefail# If a step in the middle of a fails, the whole pipe fails

-e alone misses failures in the middle of a pipe: false | true counts as success, because a pipe normally reports only its last command. pipefail makes the whole pipe fail when any part fails. -u catches typos such as $flie, which would otherwise silently become empty — dangerous in "$dir/$flie", which would then delete everything in $dir.

Control globbing with shopt

adjusts how file names are matched (). globstar lets ** reach into , and nullglob gives an empty value when nothing matches.

shopt -s globstar# ** matches into subfolders too ( 4+)
for f in **/*.sh; do echo "$f"; done# Every . file in every subfolder
shopt -s nullglob# No match gives an empty value instead of the text

shopt -s switches an on and shopt -u switches it off; shopt alone lists them all. With nullglob, for f in *.txt simply does nothing when there are no text files, instead of running once with the text *.txt. dotglob makes * include , and nocaseglob ignores case in file patterns. globstar needs bash 4.

IFS and safe line reading

(the internal ) decides where words are split. To read a file safely one line at a time, use IFS= together with read -r.

IFS=',' read -ra f <<< "a,b,c"# Split on commas into the f
while IFS= read -r line; do# Keeps spaces and
echo "$line"; done < file.txt# Reads safely one line at a time

read -r keeps as they are, and IFS= stops read from trimming spaces at the start and end of the line. If the last line of a file has no , a plain while read loses it; adding [[ -n $line ]] after read catches it. Set IFS only for one (IFS=',' read …) so the change does not affect the rest of the .

Challenge

A deletes old with $LOGDIR/*.log. Rewrite it so that it is safe even if LOGDIR is empty or unset, or contains spaces, and so that it can never delete a folder.

Hint ${VARmessage} stops when the value is missing; quote the but leave the * outside the .

Show an example answer
set -u
: "${LOGDIR:?LOGDIR is not set}"
rm -f -- "$LOGDIR"/*.log

If LOGDIR were empty, the old line would become rm -rf /*.log. :? stops the script with a message instead. Quotes keep a folder name with spaces whole, while the * stays outside the quotes so it still matches. -- protects against names starting with -, and -f without -r never deletes folders.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Manage versions with nvm

Different projects may need different . installs several versions and switches between them.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash# Install nvm (check the version number in the official docs)
nvm install 22# Install Node 22
nvm use 22# Use Node 22 in the current
node -v# Check the version you switched to

nvm installs Node inside your , so never need . A file containing a version number (22) lets anyone type nvm use in that project to get the right version. nvm default 22 sets the version new terminals start with, and nvm shows what is installed. On , the separate nvm-windows project does the same job.

npm scripts and local binaries

The in run with /.bin added to , so you can use the project's tools without installing them .

npm init -y# Make package.json quickly
npm install lodash# Add a dependency
npm install -D typescript# A development-only dependency
npm run build# Run scripts.build

Scripts can call each other ( npm run test inside another script). Extra go : npm run test -- --watch. start and test are special names that work without run ( start, npm test). node_modules/.bin is on the PATH only during npm run. So a tool installed in the project works inside scripts even when typing its name directly says ''.

One-off runs with npx

runs a ’s tool without installing it . It uses the project’s tool when available and downloads it if needed. To the , add as in the example.

npx create-next-app@latest my-app# Example of a project
npx prettier --write .# Run , or downloaded
npx --yes cowsay hello# Run once without asking for confirmation

npx first looks for the tool in the project's /.bin and downloads it to a only if it is missing. Add @ and a version to pin it (npx prettier@3 --check .). Running an unknown package with npx executes from the internet on your computer, so check the exact name — fake packages with similar names exist.

Challenge

Make a new folder hello-npm with a , a file hello.js that prints 'Hello from ', and an npm called hello that runs it. Then run the script.

Hint npm init -y creates package.json; npm pkg set can add a script without opening an editor.

Show an example answer
mkdir hello-npm && cd hello-npm
npm init -y
echo "console.log('Hello from npm')" > hello.js
npm pkg set scripts.hello="node hello.js"
npm run hello

npm init -y writes a package.json with default values. … > hello.js creates the . npm pkg set scripts.hello=… adds the script to package.json (you can also edit the file by hand). hello prints the it runs and then Hello from npm.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

AI coding agent install notes

Install them with the official or as a . Right after installing, check with the .

curl -fsSL https://claude.ai/install.sh | bash# ()
curl -fsSL https://chatgpt.com/codex/install.sh | sh#
npm install -g @xai-official/grok# (an npm alternative, as an example)
claude --version && codex --version# Check the install (example)

Each reads instructions from a file in the project ( for Claude Code, for Codex and many others), so you can tell it the project's once. They ask before running commands or changing files unless you allow it — read what they plan to run; the commands in this course are exactly what you will see. These change quickly, so check the version and update often.

Firebase and other essential CLIs

The , used for , sign-in and , is often installed together with , a fast .

npm install -g firebase-tools# Firebase CLI
firebase login# Sign in through the
firebase projects:list# List of projects
npm install -g pnpm# Install pnpm

firebase init asks questions and writes firebase.json. firebase deploy uploads the project, and firebase :start runs the services on your own computer for . Automatic builds cannot in through a browser, so they use a stored in an instead. pnpm saves space by sharing one copy of each between projects.

PATH and permission issues

lists the folders where the looks for . If a cannot be found after installation, check whether its folder is in PATH. Use which to check its location. If you use , also check that it is loaded in the current shell.

which node npm# Where the programs are
echo "$PATH"# Check the search path
npm config get prefix# Where installs go
command -v firebase || echo "not found"# Check whether it is installed

PATH is a list of folders separated by :, searched from left to right, and the first match wins — so two installations of can hide each other; which -a node shows them all. To add a folder, put PATH="$HOME/./bin:$PATH" in or ~/.bashrc and open a new . errors from install -g mean npm wants to write to a system folder; nvm, or moving npm's into your , fixes it.

Challenge

Write a short that checks whether , and are installed: print the of each one that is found, and 'missing: NAME' for each one that is not.

Hint NAME succeeds only when NAME is in your . Send its output to — only the matters.

Show an example answer
for tool in node npm git; do
  if command -v "$tool" >/dev/null; then
    echo "$tool $("$tool" --version)"
  else
    echo "missing: $tool"
  fi
done

The loop runs once per name. command -v decides which of the two parts runs. $("$tool" ) runs the tool itself to get its version: git prints 'git version 2.x', node a number with a v in front such as v22.11.0, and npm just the number.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

One-liner options by language

Almost every scripting language has an that "takes as an and runs it right away". Only the option letter differs; the way you use it is the same.

python3 -c "import sys; print(sys.version)"# one-liner
node -e "console.log(1 + 1)"# one-liner
perl -e 'print "hello\n"'# Perl one-liner
ruby -e 'puts (1..5).to_a'# Ruby one-liner
php -r 'echo PHP_VERSION;'# uses -r

The pattern AI uses to edit files

When you ask an to change , the answer almost always looks like this: a small that reads the file, replaces a and writes it back, passed in with a . The is to check first with that the thing to replace really is there.

python3 - <<'PY'# From here to PY is a Python program
p = "src/app.py"# of the file to change
s = open(p, encoding="utf-8").read()# Read the whole file
old = "before text"# The string to find (without this line you get a )
new = "after text"# The string to put in
assert old in s# Stops here if the is missing (the most important line)
s = s.replace(old, new)# Replace
open(p, "w", encoding="utf-8").write(s)# Write it back
PY# End of the heredoc — exactly at the start of the line

Heredoc in practice — generating config files

are also the cleanest way to make multi-line settings or documents inside a . If the tag is not quoted, $variables are filled in on the spot.

cat <<EOF > .env# Tag not quoted → are filled in
PORT=$PORT# $PORT is saved as its real value
EOF# Ends here
cat <<'EOF' > template.txt# Quoted → $PORT stays as
bash <<< "echo one-liner"# Pass just one line with a

sed in depth — target exactly the right lines

Put an address (line numbers or a ) in front and only those lines change. When -i edits a file directly, add an such as — you keep a , and it works the same on and .

sed -n '10,20p' log.txt# Print only lines 10 to 20
sed '/^#/d' config.txt# Delete that start with #
sed -i.bak 's/3000/8080/' config.txt# Edit the file, leaving a backup (.bak)
sed -E 's/([a-z]+)@([a-z.]+)/\2 \/ \1/' mail.txt# Swap the order with
sed 's/error/ERROR/gI' log.txt# Replace all, ignoring upper and lower case
printf 'appended\n' >> notes.txt# Add after the last line (works everywhere)

awk in depth — totals and formatting

shows its real strength in totals and formatting. Reading a table, grouping it by item and printing it neatly takes one line.

awk '{sum[$1] += $2} END {for (k in sum) printf "%-10s %5d\n", k, sum[k]}' sales.txt# Totals per item, printed as a table
awk -F, '{print $2}' data.csv# For , -F, splits on commas
awk 'NF > 0' notes.txt# Remove empty lines (a alone prints the line)
awk '{n = split($0, a, "/"); print a[n]}' paths.txt# Only the file name from a
awk 'NR == FNR {m[$1] = $2; next} $1 in m {print $1, m[$1], $2}' a.txt b.txt# Join two files
awk 'BEGIN {printf "%.2f\n", 22 / 7}'# alone makes a calculator

jq and xargs — the glue between languages

is handled with , and "run a for each result found" with . Knowing both greatly widens what you can connect with .

curl -s https://api.example.com/u | jq '.name'# One from JSON
jq -r '.items[] | .id' data.json# Spread an one item per line (-r removes the )
ls *.txt | xargs wc -l# Run a command for each file found
grep -rlZ TODO . | xargs -0 sed -i.bak 's/TODO/DONE/g'# Bulk edit with + (-Z and keep file names with spaces safe, and are kept)

Challenge

Print the sum of the numbers 1 to 100 twice: once with a short fed from a here-document, and once with and .

Hint - reads the program from ; awk can add up a and print the total in .

Show an example answer
python3 - <<'PY'
print(sum(range(1, 101)))
PY
seq 1 100 | awk '{s+=$1} END {print s}'

python3 - runs the between <<'PY' and PY; the around PY stop the from changing anything inside it. seq 1 100 writes one number per line, and awk adds each to s and prints it once at the end. Both print 5050.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Greedy and lazy matching

* and + grab as much as they can (). Add ? after them (*? and +?) to grab as little as possible () — this only works in grammar ( -P, ). In basic and , narrow the range with "not that character" instead, as in [^"]*.

echo '<b>a</b><b>b</b>' | grep -Po '<b>.*</b>'# Greedy: one piece from the first <b> to the last </b>
echo '<b>a</b><b>b</b>' | grep -Po '<b>.*?</b>'# Lazy: <b>a</b> and <b>b</b> separately
grep -Eo '"[^"]*"' data.json# Extended grammar: narrow it to "anything but a quote"
grep -Po 'id=\K\d+' app.log# — check the first part but print only the number
python3 -c "import re; print(re.findall(r'\d+(?=ms)', 'took 2300ms'))"# Lookahead (?=…) — only the number right before ms

Backreferences — the same text again

Call a captured again inside the with to find "the same text appearing again". Use it to find or fix words typed twice by mistake.

grep -Ew '([a-z]+) \1' notes.txt# Find a word repeated twice in a row (the the)
sed -E 's/\b([a-z]+) \1\b/\1/g' notes.txt# Turn a doubled word into one
grep -E '^(.)(.).?\2\1$' words.txt# Words of 4–5 letters that read the same backwards (level, noon)
sed -E 's/(.*)=(.*)/\2=\1/' config.txt# Swap the two sides of key=value

The same task on each operating system

Compare side by side how each does the same job. and use and ; uses 's and -replace.

grep -rn -E 'TODO|FIXME' .# Linux and macOS: search every file in the folder
Select-String -Path *.txt -Pattern 'TODO|FIXME'# Windows PowerShell: the same job
sed -i.bak -E 's/[[:space:]]+$//' notes.txt# Linux and macOS alike: remove trailing spaces (keeps a .bak )
(Get-Content notes.txt) -replace '\s+$', '' | Set-Content notes.txt# Windows PowerShell: the same job
findstr /R /C:"^[0-9][0-9]*$" numbers.txt# Windows : lines of digits only (no +, so [0-9]*)

rg, perl and git — faster, stronger tools

(rg) is faster than , understands as it is, and skips the files listed in on its own ( install ripgrep, install ripgrep). , built into and , is where comes from, and searches a 's and history with .

rg -n 'TODO|FIXME'# Everything under the , with line numbers
rg -o '\d{4}-\d{2}-\d{2}' -g '*.log'# Only the dates in .
perl -pe 's/(\d+)-(\d+)/$2-$1/g' a.txt# Perl: 12-34 → 34-12
perl -pi -e 's/foo/bar/g' *.txt# Perl: edit several files in place
git log --grep='fix.*login' -i --oneline# Search messages with a regex
git log -G 'apiKey' --oneline# Search code changes with a regex

More of Python's re

's re uses grammar, so \w \s, and all work. Write as r"…" — a plain "" turns into a backspace character.

import re# Load the
re.search(r"\d+", "a12b").group()# The first match → "12"
re.findall(r"\d+", "a12b345")# Every match → ["12", "345"]
re.sub(r"\s+", " ", "a b")# Replace → "a b"
re.split(r"[,;]\s*", "a, b;c")# Split with a regex → ["a", "b", "c"]
m = re.match(r"(?P<y>\d{4})-(?P<m>\d{2})", "2026-10")# Named groups — m["y"] is "2026"
pat = re.compile(r"error", re.I)# Build it once; re.I ignores case

awk and regex

uses . // checks the whole line, $3 ~ /pattern/ checks only that , and replaces. Give -F a to split on several at once.

awk '/ERROR|WARN/' app.log# The same as -E
awk '$4 ~ /^user=a/' app.log# Lines whose 4th column starts with user=a
awk '$3 !~ /INFO/ {print $3, $4}' app.log# Columns 3 and 4 of lines that are not INFO
awk '{gsub(/[0-9]+ms/, "N ms"); print}' app.log# Replace every value like 2300ms on the line
awk -F'[=: ]+' '{print $2}' config.txt# Treat =, : and runs of spaces all as separators

Challenge

contacts.txt has e-mail addresses mixed in among its lines. Print every address once, sorted, and nothing else.

Hint -o prints only the part, -E allows + and {2,}, and -u removes duplicates.

Show an example answer
grep -oE '[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}' contacts.txt | sort -u

The is: letters, digits or ._%+- (the name), @, a , a dot and at least two letters. -o prints each match on its own line, even several per line. Real e-mail rules are more complicated, but this pattern is good enough for .

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Where options may stand, and --

normally stand right after the , but tools () also accept them after the , while BSD tools () do not. That is why the safe habit is command, options, target. Everything is read as a target, never as an option, which rescues names that begin with a dash.

ls -l notes.txt# The order that works everywhere
ls notes.txt -l# Works on Linux, fails on macOS
rm -- -weird.txt# Delete a file whose name starts with a dash
grep -- -v notes.txt# Search for the text "-v" instead of using the option

Reading brackets in man and --help

The first line of man or is the itself, written in one shared notation. Square brackets [ ] mark what you may leave out, ... means the slot may repeat, a bar | offers a choice, and words in capitals or italics are slots you fill in. Read that one line and you know how to call a you have never used.

ls --help | head -5# The pattern is in the first lines
man ls# The full manual — press q to leave
man -k copy# Find a command by what it does
type cd# Is this name a , a built-in or an ?

Exit code 0 and $?

Every ends with an : 0 means success and anything else means failure. $? holds the of the command that just finished, and this is exactly what look at. Many commands offer a quiet so you can use the code alone, without any output.

ls notes.txt; echo $?# Success prints 0
ls missing.txt; echo $?# Failure prints something other than 0
grep -q echo todo.sh && echo found# -q prints nothing and only sets the code
true; echo $?# true always succeeds, false always fails

Challenge

du is a you have not met yet. Work out its with , then list the five biggest things in your Downloads folder, largest last.

Hint The first line of --help is the pattern. -s gives one total per , -h uses units people read, and -h understands those units.

Show an example answer
du --help | head -20
du -sh ~/Downloads
du -sh ~/Downloads/* | sort -h | tail -5

du --help shows the pattern du []... [FILE]..., which tells you both slots are optional and may repeat. So - goes in the option slot, ~/Downloads/* in the target slot, and the output flows through sort -h into -5. You never had to memorize du — the pattern was enough.

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

A closer look at disks: fdisk -l, blkid and parted print

draws the tree, and three more look inside it. -l prints the in , shows the type and the — a serial number that never changes, unlike the name , which can change when are added — and shows the table in parted's own words. All of them only read.

sudo fdisk -l /dev/sda# The partition table, sector by sector
sudo blkid# File system type and UUID of each partition
sudo parted -s /dev/sda print# The same table, as parted sees it
cat /proc/partitions# What the itself knows

A disk inside a file: loop devices

You do not need a spare to practise. A makes an ordinary file act like a disk: create a file of the size you want, , and it with -o . for and USB installers work exactly this way.

truncate -s 16M usb.img# An empty 16 file
mkfs.vfat usb.img# Format it like a
sudo mkdir -p /mnt/usb && sudo mount -o loop usb.img /mnt/usb# Mount the file as if it were a disk
df -h /mnt/usb# A 16 MB "disk" that is really a file

Making the disk yours, and mounting it at start-up

A freshly belongs to . gives the to you, so you can write without . A also lasts only until the computer restarts; on a real you add a line to so it is mounted at every start, preferably with the from instead of the name. Test the line with mount -a before rebooting — a broken fstab can stop a server from starting.

sudo chown you /mnt/data# The mount point now belongs to you
echo "mine" > /mnt/data/mine.txt# No sudo needed any more
echo "/dev/sda1 /mnt/data ext4 defaults 0 2" | sudo tee -a /etc/fstab# Mount it at every start (use UUID=… on a real server)
sudo umount /mnt/data && sudo mount -a && df -h /mnt/data# Check that the fstab line works

Package managers side by side

Every does the same few things — find, install, list, remove — with slightly different words. and install for the whole system (so they need ), while and install into a or a project folder (so they do not). pip freeze and are the recipes that let you rebuild the same setup anywhere.

apk search htop# Find a package by name
sudo apk del figlet# Remove a again
pip freeze > requirements.txt# Write down the packages in this venv
npm ls# The packages this project uses

curl in more depth — POST, headers, time limits and retries

So far you have only things (). To send something to an , switch the method with -X , put the content to send after -d, and add (the content type, an ) with -H. -v shows the whole and that go back and forth, which is how you find out why something does not work. In case the is slow or drops for a moment, set how long to wait with --max-time and try again with --retry. In , add -f so the tells you about a failure. Keep secrets such as tokens in instead of writing them into the .

curl -X POST -H "Content-Type: application/json" -d '{"name":"you"}' https://httpbin.org/post# Send — the other side answers with what it received
curl -v -o /dev/null https://sonub.com/robots.txt# Show every request and response header (> is what you sent, < is what came back)
curl -fsS --max-time 10 --retry 3 -o /dev/null https://sonub.com/up && echo OK# Within 10 seconds, retrying up to three times — the core of a server-check script
curl -H "Authorization: Bearer $TOKEN" https://api.github.com/user# Keep the access token in an environment variable (without one the answer is 401)

Challenge

Split the practice into two halves. Format the first half with and the second with (vfat), them on / and /mnt/share, and compare the two with -h.

Hint One can make a table and two : 1MiB to 50% and 50% to 100%. Then and mkfs.vfat, two and two mounts.

Show an example answer
sudo parted -s /dev/sda mklabel gpt mkpart data ext4 1MiB 50% mkpart share fat32 50% 100%
lsblk /dev/sda
sudo mkfs.ext4 /dev/sda1
sudo mkfs.vfat /dev/sda2
sudo mkdir -p /mnt/data /mnt/share
sudo mount /dev/sda1 /mnt/data
sudo mount /dev/sda2 /mnt/share
df -h /mnt/data /mnt/share

gpt is the modern partition table, and in a gpt table mkpart takes a name first (data, share). The two partitions become sda1 and sda2, and each gets its own . df -h lists both with their sizes: FAT keeps less room for itself, so its free space looks a little different from ext4.

Do this one in the practice terminal, not on your own computer — it erases and rebuilds the disk. Press Reset session first so the disk is blank.

Same letter, different meaning — options belong to a family

A short is a single letter, so letters clash. -v is the in and , "only lines that do not match" (invert) in , and "verbose" in and ssh. -s is the name in skills and quiet in curl. A long option ( · --verbose · --silent) carries its meaning in its name, so it never confuses anyone — use long options in and they will be easier to read later.

node -v# in node, -v is the version
grep -v '#' patterns.txt# in grep, -v leaves out the lines with #
curl -v https://sonub.com -o /dev/null# in curl, -v shows the whole conversation
npx skills add --help | grep -- "-s,"# find what -s means in this family

Passing options to an install script — bash -s -- and other shapes

take too. In … | the arrives on , so you write its options after bash -s -- — -s means "read the script from input" and -- means "from here on, the words are for the script". Some, like , use the shape /bin/bash -c "$(curl -fsSL )" — takes the whole script text and hands it to bash -c. Whatever the shape, the is the same: it, then bash runs it.

curl -fsSL https://example.com/install.sh | bash -s -- --version 1.2.0# pass 1.2.0 to the script (example URL)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"# installing Homebrew () — another shape of the same pattern
bash <(curl -fsSL https://sonub.com/install/hello.sh)# hand the fetched text to bash as if it were a file
curl -fsSL https://sonub.com/install/hello.sh -o hello.sh && bash hello.sh# save it, read it, then run it

Package managers share the pattern too — manager · job · name

differ by and language, but they share one shape — manager · job · name. Even the job names are alike: install and add install, remove, uninstall and del remove, list and list, update and upgrade move to a newer . Learn one, and for the rest you change only a few words.

sudo apk add figlet# (this practice )
sudo apt install figlet# and
brew install figlet# ()
pip install cowsay# — inside a
npm install ms# a project — into

Challenge

The other follow the same as add. In the practice , install the gp skill (add), look at the list (list), then remove it again (remove) using only .

Hint The -g (global) and -y (do not ask) you used with add mean the same with list and remove. remove takes the name of the skill to remove in its slot.

Show an example answer
npx skills add https://sonub.com -g -s gp -a claude-code -y
npx skills list -g
npx skills remove --help
npx skills remove gp -g -y
npx skills list -g

add · list · remove all have the shape " subcommand [target] []". With -g all three look at the global scope, and -y does not ask. Even if you have never used remove, the first line of its --help (skills remove [skills...] [options]) tells you to put gp in the target slot — you used the pattern, not .

Try the task before opening the answer. Check that the example files exist, and create practice files if needed. If you work on your own computer, use a practice folder too.

Adapted from Learn X in Y Minutes (, ). Some are reference-only.

Lesson complete!Excellent — you just took another step forward.