The Command Line in 10 Minutes

The terminal looks hostile because it gives you nothing to click. In exchange, it gives you something better: every action becomes a sentence, and sentences can be repeated, combined, and automated. This page is the grammar — plus translations for the three errors that make beginners close the laptop.

01The model: verb, flags, object

Every command is a tiny sentence: a verb (the program), flags that modify it (usually starting with -), and the object to act on. That's the entire grammar. When you see a scary one-liner online, it's just this pattern, three times, glued together.

ls  -l  /tmp
│   │   └── object: which directory
│   └────── flag: "long format" (more detail)
└────────── verb: list files

# the two lifelines, always available:
ls --help        # quick summary of any command
man ls           # the full manual (q to quit)

03Files: copy, move, delete — no trash can

Four verbs: mkdir, cp, mv (which is also "rename" — moving to a new name), and rm. Now the sentence that must be said plainly: rm does not use a trash can. There is no undo, no restore, no "recently deleted". It is the most honest command on the system.

mkdir notes             # make a directory
cp report.txt backup.txt   # copy
mv report.txt final.txt    # rename (same thing as move)
mv final.txt notes/        # actually move

rm old.txt              # delete a file. FOREVER.
rm -r old_project/      # delete a folder and everything in it
The famous disaster
rm -rf / and its cousins (a stray space: rm -rf ~ /tmp deletes your home) are how careers end. Two working defenses: never use -f reflexively — it exists to silence warnings you should read; and before any rm with a wildcard, run ls with the same pattern first to see exactly what will die. ls *.log, look, then rm *.log.

04Reading files without opening an editor

You rarely need an editor just to look. cat dumps a file, less pages through it, head/tail show the ends. The one that feels like a superpower: tail -f follows a log file live — watching errors appear as users hit them.

cat config.yml          # dump the whole file
less huge.log           # scroll comfortably (q quits, / searches)
head -20 data.csv       # first 20 lines — check a CSV's columns
tail -50 app.log        # last 50 lines — where the crash is
tail -f app.log         # watch it grow, live. Ctrl+C to stop

05grep: find the needle

grep prints the lines that match a pattern. That's all — and it's enough to answer "where is the error", "where is this setting", "where in the code is this function used". Three flags do 95% of the work: -i (ignore case), -r (search folders recursively), -n (show line numbers).

grep "ERROR" app.log            # lines containing ERROR
grep -i "timeout" app.log       # case-insensitive
grep -rn "api_key" src/         # search a whole folder, with line numbers
grep -c "ERROR" app.log         # just count the matches
grep -v "DEBUG" app.log         # invert: lines WITHOUT the pattern

06Pipes: the whole philosophy in one character

The | character feeds one command's output into the next command's input. This is the terminal's entire philosophy: small tools that each do one thing, snapped together like hose segments. You compose answers to questions nobody wrote a program for.

# "what are the 5 most common errors in this log?"
grep "ERROR" app.log | sort | uniq -c | sort -rn | head -5
#      find them      group same lines together, count,
#                     sort by count desc, top 5

# > saves output to a file (OVERWRITES), >> appends
grep "ERROR" app.log > errors.txt
echo "new line" >> notes.txt
The trap in >
> overwrites the target file before the command runs. Which means grep "x" data.txt > data.txt — filtering a file into itself — instantly empties it. The shell truncates data.txt first, then grep reads the now-empty file. Write to a new name, then mv it back.

07PATH: why "command not found" after you just installed it

When you type python, the shell searches a list of folders — the PATH — in order, and runs the first match. That's the entire mechanism. And it explains the most confusing beginner error: you installed a tool, the installer put it in a folder not on the list, and the shell honestly reports it can't find it.

$ mytool
bash: mytool: command not found

echo $PATH               # the list, colon-separated
which python             # which folder won for this command?

# the fix: add the tool's folder to the list (in ~/.bashrc or ~/.zshrc)
export PATH="$HOME/.local/bin:$PATH"
# then restart the terminal — or:  source ~/.bashrc
The ritual everyone forgets
You edited ~/.bashrc, and nothing changed — because that file only runs when a terminal starts. Either open a new terminal or source ~/.bashrc in this one. "Edit, forget to source, conclude the fix failed, try something worse" is a rite of passage; skip it.

08Permissions: what "denied" actually wants

Every file has an owner and a set of switches: who may read, write, execute. Permission denied means a switch is off for you — nothing is broken. Two honest rules: reach for sudo only when the task is genuinely system-wide, and treat chmod 777 as the wrong answer that happens to shut the error up.

$ ./deploy.sh
bash: ./deploy.sh: Permission denied

ls -l deploy.sh          # -rw-r--r--  ← no 'x': not executable
chmod +x deploy.sh       # flip the execute switch
./deploy.sh              # runs ✓

sudo apt install nginx   # sudo = do it as administrator
Honest take
Stack Overflow's favorite bad advice is sudo chmod -R 777 . — "make everything writable by everyone". It "works" the way removing your front door "fixes" losing your keys. If a specific tool keeps needing sudo in your own home directory, something's misinstalled — fix the ownership (chown), don't open the switches.

09Killing things: Ctrl+C and friends

Programs hang. Ports stay occupied. Here's the escalation ladder: Ctrl+C politely stops the thing in front of you; kill asks a background process to quit; kill -9 stops asking. And the daily classic — "port already in use" — is a two-line fix.

Ctrl+C                   # stop the current command (most of the time)
Ctrl+D                   # "I'm done typing" / exit the shell

Error: listen EADDRINUSE: address already in use :3000
lsof -i :3000            # who's squatting on port 3000? → shows PID
kill 12345               # ask nicely (PID from above)
kill -9 12345            # stop asking (last resort — no cleanup happens)

10Cheat sheet

Keyed by the sentence in your head.

# "where am I / what's here / go there"
pwd · ls -la · cd folder · cd .. · cd -

# "what will this wildcard delete?"  (look BEFORE rm)
ls *.log    # then, and only then:  rm *.log

# "what's in this file?"
less file    # q quits, / searches

# "watch the log live"
tail -f app.log

# "where does X appear in this project?"
grep -rn "X" .

# "top 5 most common errors"
grep "ERROR" log | sort | uniq -c | sort -rn | head -5

# "command not found (but I installed it)"
echo $PATH · which cmd · add folder to PATH, source ~/.bashrc

# "permission denied on my script"
chmod +x script.sh

# "port already in use"
lsof -i :3000 → kill <PID>

That's a working vocabulary. The deeper shift: the terminal turns "things I do" into "sentences I can save" — and saved sentences become scripts, and scripts become automation. That's the actual reason every developer ends up here. Where Git and Python live: right in this terminal — Git and Python are the natural next reads.