Chapter 1 · Your Developer Toolbox
The Terminal: Files, Paths and Commands
- Page 2 of 22
- 17 min read
Every tool in this tutorial — Git, Python, pip and uv, curl, FastAPI's server, Docker later on — is driven from the terminal. So is almost every server an AI model runs on: when a training job fails on a cloud GPU at 2 a.m., there is no File Explorer, just a prompt, a log file and the commands on this page. Ten or so commands cover most of what you will do every day, and they are worth learning properly once.
This page sets up a small practice folder with a dataset and a server log, and you will move around it, create, copy and delete files, search them and chain commands together — exactly the moves you will make when you inspect a CSV before training a model or hunt for errors in an API's log.
What you will learn
- What the terminal, the shell and a command are, and which shell to use on Windows, macOS and Linux
- How files and folders are organised, and the difference between absolute and relative paths (
/,~,.,..) - The everyday commands:
pwd ls cd mkdir touch cp mv rm cat echo head tail grep - Wildcards, pipes (
|) and redirection (>,>>,2>) - How to get help when you forget a flag
Terminal, shell and command
Three words get mixed up all the time. They are different layers:
| Word | What it is | Examples |
|---|---|---|
| Terminal (terminal emulator) | The window you type into. It only shows text and sends your keystrokes on. | Windows Terminal, macOS Terminal, iTerm2, GNOME Terminal, the terminal panel inside VS Code |
| Shell | The program running inside the terminal that reads a line you type, works out what it means and runs it. | bash (Linux, Git Bash on Windows), zsh (the macOS default), PowerShell and cmd (Windows) |
| Command | A program or a shell built-in that does one job. | ls, git, python, curl, cd |
This course writes every command for bash. zsh on macOS understands all of them. On Windows, use Git Bash (it is installed with Git for Windows, which you need anyway) or, even better for backend work, WSL 2 (a real Ubuntu inside Windows: wsl --install in an administrator PowerShell, then restart). PowerShell is a fine shell too, but its commands differ; a table near the end of this page lists the ones that matter.
When a guide shows a line like $ ls, the $ is the prompt — the shell's way of saying "ready". You type only what comes after it. This course leaves the prompt out, so every block can be copied as it is. Each command line is a command name, then options (flags such as -n 3 or --help), then arguments (usually file names), separated by spaces.
Where am I? Files, folders and paths
Your files live in one big tree of folders (directories). On Linux and macOS the top of the tree is / (the root), and your own files live in your home folder, /home/<you> on Linux or /Users/<you> on macOS. The shell always has a current working directory, the folder you are "standing in". pwd (print working directory) shows it:
pwd/home/nadiaThe learner in this course is Nadia, so her home folder is /home/nadia; yours shows your own name. Let us make a practice folder and step into it. mkdir -p creates a folder and any missing parent folders on the way; cd (change directory) moves you:
mkdir -p ~/projects/terminal-practice
cd ~/projects/terminal-practice
pwd/home/nadia/projects/terminal-practiceAbsolute and relative paths
A path names a file or folder. There are two kinds, and mixing them up is the most common reason a script "cannot find the file":
| Path | Meaning | Example |
|---|---|---|
| Absolute | Starts at the root /; means the same thing wherever you are | /home/nadia/projects/terminal-practice/data |
| Relative | Starts from the current folder | data/reviews.csv |
~ | Your home folder (the shell expands it) | ~/projects |
. | The current folder | ./run.sh |
.. | The parent folder, one level up | ../other-project |
Python follows the same rule: open("data/reviews.csv") is relative to the folder you ran Python from, not to the folder the script lives in. That is why the same script works from one folder and fails with FileNotFoundError from another.
Creating files: echo, redirection, touch and cat
echo prints its arguments. With > its output goes into a file instead of the screen (redirection); >> appends. cat prints a file:
echo "Notes for the Git, APIs and Backend course" > notes.txt
cat notes.txt
echo "Day 1: the terminal" >> notes.txt
cat notes.txtNotes for the Git, APIs and Backend course
Notes for the Git, APIs and Backend course
Day 1: the terminal
>replaces the whole file without asking.>>adds to the end. Writing>when you meant>>silently wipes what was there — check twice when the file matters.
touch creates an empty file (or updates the time stamp of one that exists). ls lists what is in a folder; -a also shows hidden entries (names starting with a dot, like .env and .git later in this course), and -F marks folders with a /:
touch app.py .env
mkdir data logs
ls
ls -a
ls -Fapp.py
data
logs
notes.txt
.
..
.env
app.py
data
logs
notes.txt
app.py
data/
logs/
notes.txt. and .. appear in every folder: they are the folder itself and its parent, the same ones you use in paths. ls -l (long format) adds permissions, owner, size and modification time on each line, and ls -lh prints sizes as 4.0K or 2.1M; try them, they differ on every machine.
Writing a whole file at once
For files with several lines, a here-document is handy: everything between <<'EOF' and the line EOF goes into the command's input. Create a small dataset of course reviews and a log from an LLM-backed API — you will explore both for the rest of the page:
cat > data/reviews.csv <<'EOF'
id,course,rating,comment
1,Python for AI,5,Clear and practical
2,SQL and Data Handling,4,Good exercises
3,Python for AI,4,Loved the projects
4,Maths for AI,3,A bit fast
5,SQL and Data Handling,5,Joins finally make sense
6,Python for AI,2,Setup was hard on Windows
7,Maths for AI,5,Great intuition for vectors
EOF
cat > logs/app.log <<'EOF'
2026-01-15 10:00:01 INFO POST /v1/chat 200 812ms tokens=153
2026-01-15 10:00:04 INFO GET /health 200 2ms
2026-01-15 10:00:09 ERROR POST /v1/chat 502 30004ms upstream timeout
2026-01-15 10:00:12 INFO POST /v1/chat 200 640ms tokens=98
2026-01-15 10:00:15 WARN POST /v1/chat 429 5ms rate limited
2026-01-15 10:00:21 ERROR POST /v1/chat 500 12ms KeyError: 'choices'
2026-01-15 10:00:30 INFO GET /v1/courses 200 9ms
EOF
ls data logsdata:
reviews.csv
logs:
app.logThe quotes in 'EOF' tell bash to copy the lines exactly, without expanding anything like $HOME inside them. You will use this pattern again to write small config and JSON files.
Looking inside files: head, tail, wc
A real dataset may have millions of lines; cat on it floods your screen. Look at the start and end instead, and count lines:
head -n 3 data/reviews.csv
tail -n 2 logs/app.log
wc -l data/reviews.csv logs/app.logid,course,rating,comment
1,Python for AI,5,Clear and practical
2,SQL and Data Handling,4,Good exercises
2026-01-15 10:00:21 ERROR POST /v1/chat 500 12ms KeyError: 'choices'
2026-01-15 10:00:30 INFO GET /v1/courses 200 9ms
8 data/reviews.csv
7 logs/app.log
15 totalhead showed the header row and the first two reviews, which is exactly what you check before loading a CSV with pandas: are the column names right, is the separator a comma? wc -l counts lines: 8 lines is 1 header plus 7 reviews. On a server, tail -f logs/app.log keeps the file open and prints new lines as they arrive; press Ctrl+C to stop it (Ctrl+C stops any running command).
Searching: grep
grep prints the lines that contain a pattern. -n adds line numbers, -i ignores upper/lower case, -c counts matching lines instead of printing them, -v inverts the match, and -r searches every file under a folder:
grep ERROR logs/app.log
grep -n "429" logs/app.log
grep -c "/v1/chat" logs/app.log
grep -i "windows" data/reviews.csv
grep -r "Python for AI" .2026-01-15 10:00:09 ERROR POST /v1/chat 502 30004ms upstream timeout
2026-01-15 10:00:21 ERROR POST /v1/chat 500 12ms KeyError: 'choices'
5:2026-01-15 10:00:15 WARN POST /v1/chat 429 5ms rate limited
5
6,Python for AI,2,Setup was hard on Windows
./data/reviews.csv:1,Python for AI,5,Clear and practical
./data/reviews.csv:3,Python for AI,4,Loved the projects
./data/reviews.csv:6,Python for AI,2,Setup was hard on WindowsNotice grep -r printed the file name in front of each match because it searched more than one file. When an LLM endpoint misbehaves in production, grep ERROR on its log is very often the first command anyone runs; here it found an upstream timeout (the model provider was too slow) and a KeyError (the code expected a field the response did not have).
Pipes: small commands, combined
The pipe | sends one command's output into the next command's input. Each tool does one small job; chained, they answer real questions. How many errors are there? Which ratings appear, and how often?
grep ERROR logs/app.log | wc -l
tail -n +2 data/reviews.csv | cut -d, -f3 | sort | uniq -c2
1 2
1 3
2 4
3 5Read the second line left to right: tail -n +2 prints from line 2 onward (skipping the header), cut -d, -f3 keeps the third comma-separated field (the rating), sort puts equal values next to each other, and uniq -c counts each run of equal lines. That is a tiny value_counts() without starting Python. (cut splits on every comma, so it is only safe for simple CSVs; a comment containing a comma would shift the columns — for real data use pandas.)
Copying, moving, renaming and deleting
| Command | Does |
|---|---|
cp a b | Copies file a to b (cp -r for a folder) |
mv a b | Moves a to b; in the same folder that is a rename |
rm a | Deletes file a (rm -r for a folder and everything in it) |
rmdir d | Deletes folder d only if it is empty |
mkdir backup
cp data/reviews.csv backup/reviews-2026-01-15.csv
mv notes.txt course-notes.txt
mv app.py backup/
ls
ls backupbackup
course-notes.txt
data
logs
app.py
reviews-2026-01-15.csvWhen the last argument of cp or mv is an existing folder, the file goes into it, keeping its name. Now delete the backup folder:
rm -r backup
lscourse-notes.txt
data
logsThe terminal has no recycle bin.
rmdeletes at once and for good, andrm -rtakes a whole folder tree with it. Runpwdandlsfirst, never runrm -rfon a path you built from a variable that might be empty, and userm -i(asks before each file) while you are learning. Git, from Version Control and Your First Repository on, is your real safety net.
Moving around: cd, ., .. and ~
cd data
pwd
ls ..
cd ..
pwd
cd ~
pwd
cd -/home/nadia/projects/terminal-practice/data
course-notes.txt
data
logs
/home/nadia/projects/terminal-practice
/home/nadia
/home/nadia/projects/terminal-practicecd .. goes up one level, cd ~ (or just cd) goes home, and cd - jumps back to the previous folder and prints where it landed. Two keys save a lot of typing: Tab completes file and folder names (press it twice to see the choices), and the Up arrow brings back earlier commands.
Wildcards
The shell expands wildcards into matching file names before the command runs: * matches any characters, ? exactly one, [0-9] one character from a set. echo shows the expansion:
touch run-1.log run-2.log run-10.log
echo *.log
echo run-?.log
ls data/*.csvrun-1.log run-10.log run-2.log
run-1.log run-2.log
data/reviews.csvTwo things to notice. run-?.log did not match run-10.log, because ? stands for exactly one character. And names sort as text, character by character, so run-10 comes before run-2; that is why experiment folders are usually zero-padded (run-01, run-02, … run-10). Since the shell expands wildcards, rm *.log deletes every .log file in the folder — run echo *.log first to see what it will hit.
Spaces in names
The shell splits a command line at spaces, so a file name with a space must be quoted:
echo "draft" > "meeting notes.txt"
cat "meeting notes.txt"
cat meeting notes.txtdraft
cat: meeting: No such file or directory
cat: notes.txt: No such file or directoryWithout quotes cat received two arguments, meeting and notes.txt, and looked for two files. For files you create yourself, avoid spaces altogether: meeting-notes.txt or meeting_notes.txt.
Errors, exit codes and the two output streams
Every command ends with an exit code: 0 means success, anything else means failure. $? holds the last one. Commands also have two output streams: normal output (stdout) and error messages (stderr). > redirects only stdout; 2> redirects stderr:
ls no-such-folder
echo "exit code: $?"
ls no-such-folder 2> errors.txt
cat errors.txt
ls data > listing.txt 2>&1
cat listing.txtls: cannot access 'no-such-folder': No such file or directory
exit code: 2
ls: cannot access 'no-such-folder': No such file or directory
reviews.csv2>&1 means "send stderr to wherever stdout goes", so command > file 2>&1 captures everything. You will use exactly that later to run an API server in the background with its log in a file. Exit codes matter more than they look: CI pipelines (GitHub Actions), Docker health checks and test runners all decide "passed or failed" from them.
Getting help
Nobody remembers every flag. Three ways to look things up:
head --help | head -n 3
type cd
type grepUsage: head [OPTION]... [FILE]...
Print the first 10 lines of each FILE to standard output.
With more than one FILE, precede each with a header giving the file name.
cd is a shell builtin
grep is /usr/bin/grepcommand --helpprints a short summary on Linux, Git Bash and WSL (macOS's built-in tools mostly do not support it; useman).man commandopens the full manual: arrow keys to scroll,/wordto search,qto quit.typetells you whether a name is a shell built-in (cdhas to be one, because it changes the shell's own folder) or a program file on disk.
Git has the same pattern: git help commit or git commit -h. And asking an AI assistant "what does tail -n +2 do?" is a perfectly good habit — then check its answer with --help.
Windows: Git Bash, WSL and PowerShell
In Git Bash or WSL every command on this page works as written. Two things to know on Windows: your C: drive appears as /c/ in Git Bash and /mnt/c/ in WSL, and Windows paths use \ while bash uses /. If you work in PowerShell instead, these are the differences that matter (many bash names exist there as aliases, but their options differ):
| bash | PowerShell |
|---|---|
pwd, cd, ls | Work as aliases (Get-Location, Set-Location, Get-ChildItem); hidden files need ls -Force |
cat file | Get-Content file (cat works as an alias) |
head -n 3 file / tail -n 3 file | Get-Content file -TotalCount 3 / Get-Content file -Tail 3 |
touch file | New-Item file |
mkdir -p a/b | mkdir a/b (creates parents by itself) |
rm -r folder | Remove-Item folder -Recurse |
grep ERROR file | Select-String ERROR file |
wc -l file | (Get-Content file).Count |
Our recommendation: install Git for Windows and use Git Bash for this course, or WSL 2 if you can. Servers and Docker containers run Linux, so bash habits pay off directly.
Where this shows up in AI and backend work
- Checking data before training:
head,wc -landgrep -con a CSV or JSONL file tell you in a second whether the file is complete and shaped the way your code expects. - Debugging a deployed API: on a server or inside a container you
cdto the app,tailthe log andgrepforERROR, a request ID or a status code like429. - Running long jobs:
python train.py > train.log 2>&1keeps a complete record of a training run you can search later. - Automation: CI workflows, Dockerfiles and deployment scripts are just these commands written down — you will write all three in this course.
Common mistakes
- Running a command in the wrong folder.
No such file or directoryusually means your current folder is not the one you think. Runpwdandls, thencdto the right place or use an absolute path. - Typing the prompt. Copying
$ lsincluding the$gives$: command not found. Type only what comes after the prompt. - Unquoted names with spaces.
cd My Documentstries to go toMy. Quote it (cd "My Documents") or press Tab to let the shell escape it for you. >instead of>>. One character empties a file. Use>>to add, and keep important files under Git so a mistake can be undone.- A careless
rm -rf. There is no undo. Check the path withls(orechofor wildcards) before deleting.
Try it yourself
- Easy: In
~/projects/terminal-practice, create the foldersexperiments/run-01andexperiments/run-02with one command, put a fileparams.txtcontaininglearning_rate=0.01intorun-01, and print it. - Medium: Save every
POST /v1/chatline fromlogs/app.logintochat.log, then print how many lines it has. Then add theGET /healthline to the end of the same file without losing what is there. - Hard: From
logs/app.log, list each HTTP status code and how many times it appears, most frequent first. (Hint: the status code is the 6th space-separated field;cut -d' ' -f6breaks on the double space afterINFO, so tryawk '{print $6}', thensort,uniq -candsort -rn.)
Answers
1. mkdir -p accepts several paths at once:
cd ~/projects/terminal-practice
mkdir -p experiments/run-01 experiments/run-02
echo "learning_rate=0.01" > experiments/run-01/params.txt
cat experiments/run-01/params.txt
ls experimentslearning_rate=0.01
run-01
run-022. > creates the file, >> appends:
grep "POST /v1/chat" logs/app.log > chat.log
wc -l chat.log
grep "GET /health" logs/app.log >> chat.log
wc -l chat.log5 chat.log
6 chat.log3. awk splits on any run of spaces, so the extra space after INFO does not matter. The final sort -rn sorts by the count, largest first:
awk '{print $6}' logs/app.log | sort | uniq -c | sort -rn 4 200
1 502
1 500
1 429Summary
- The terminal is the window, the shell (bash, zsh, PowerShell) interprets what you type, commands do the work. On Windows use Git Bash or WSL 2 for this course.
- Absolute paths start at
/; relative paths start from the current folder (pwd).~is home,.here,..one level up. ls cd mkdir touch cp mv rmmanage files;cat head tail wc greplook inside them.rmhas no undo.- Wildcards are expanded by the shell;
|chains commands;>overwrites,>>appends,2>captures errors,> file 2>&1captures everything. - Exit code 0 means success — CI, Docker and test tools all rely on it.
--help,manandtypeanswer "how does this work?".
Next: Environment Variables, .env Files and Virtual Environments uses the terminal to set up a Python project properly: where settings and API keys live, how the shell finds programs, and how each project gets its own packages with venv and uv.