The Shell, Your First Commands, and the Environment
Context
The terminal is the window. The shell is the program inside it that reads what you type, works out which command you meant, and runs it. Every line you enter there is a command name followed by the things it acts on and the switches that change how it behaves.
Nearly all machine learning tooling is command-line first. You install packages, launch a training run, connect to a machine with a graphics card that has no screen attached, move a dataset between folders, and check what a job produced, all from this one prompt. The graphical alternatives either do not exist or stop existing the moment the work moves to a server.
Working without it means being unable to touch any machine you cannot see, and being unable to reproduce a step you performed by clicking. It also means being unable to explain the most common failure of all, the one where a package is definitely installed and the command still reports that it is not found, because the name you typed resolved to a different program than the one you installed into. Lesson 0.1.4 already had you typing cd and pwd to move along paths, and this lesson is about the program that read those lines.
Goal
- Open a terminal and identify exactly which shell program is reading your keystrokes.
- Run a command with arguments and flags, distinguishing an argument from a flag and combining multiple flags into a single command, and predict the output BEFORE you press Enter.
- Navigate the filesystem tree by hand using
pwd,cd, andlswith both absolute and relative paths. - Create, copy, move, and delete files and directories using
mkdir,touch,cp,mv, andrm. - Read and set an environment variable, and explain what
exportchanges about who can see it. - Trace the executable lookup behind a bare command name, using
whichandtypeto say which program will run, and make a setting outlive the session through the shell profile, saying why the running shell does not see the edit.
Prerequisites
Both are optional and the lesson assumes neither. They are here if you would rather see the picture before you build it.
- YouTube - "The 50 Most Popular Linux & Terminal Commands - Full Course for Beginners" (freeCodeCamp.org, Colt Steele). Watch from "Using The Terminal" at 29:43 to the end of the "cd" chapter at 1:00:40: typing at the prompt and running
pwd,lsandcd. This gives you the visual mental model of typing → result. - Ubuntu Tutorials - "The Linux command line for beginners", sections "1. Overview" through "5. Moving and manipulating files." Free, openly accessible.
Table of Contents
- Terminal
- Shell
- Command
- Argument
- Flag / Option
- Environment Variables and
PATH - Executable Lookup and the Profile
Theory
Terminal
What. The terminal is the text window where you type instructions to the machine and read its text replies. Nothing more, it is a window for characters in and characters out.
Why. A graphical interface (windows, buttons, drag-and-drop) only exposes the actions a designer decided to draw a button for. The terminal exposes the whole machine, and every action you take is a line of text you can save, repeat, and send to someone else. In ML work this is non-negotiable: you train models on remote servers that have no screen, no mouse, and no buttons, only a terminal reachable over the network.
When. Any time you need precise, repeatable control, installing tools, moving a dataset, or launching a training script, you reach for the terminal instead of a file explorer.
BEFORE ANYTHING ELSE, you must have a terminal that runs the same commands this lesson uses. The commands here are bash commands, the standard on Linux.
- Linux: A terminal is already installed. Open it (often
Ctrl+Alt+T). - Mac: Open the built-in app named Terminal (Spotlight → type "Terminal").
- Windows: Windows does NOT run bash by default, which is why this bootcamp runs on WSL (Windows Subsystem for Linux, a real Linux environment that runs inside Windows), already installed and set up on the setup page. Search your Start menu for Ubuntu (or run
wslfrom an existing terminal) to open it, and a real bash terminal appears. From that point on, Windows users type the exact same commands as everyone else.
Once a terminal is open, you will see a prompt, a short line of text ending in $ that waits for you to type. The prompt is the terminal saying "I am ready for one instruction."
# A typical prompt. You type AFTER the $, do not type the $ itself.
(bootcamp) goku@machine:~$(bootcamp) is the environment from the setup page, active as it must always be. The rest before the $ tells you who you are (goku), which machine (machine), and where you currently are (~, which means your home directory). The $ is the boundary: everything you type lands to the right of it.
DON'T:
- DON'T confuse the terminal with the shell. The terminal is only the window that shows text and captures keystrokes, the program that actually understands what you type is the shell (next subtitle).
- DON'T type the
$from the prompt when copying a command. The$marks where input begins, it is not part of the command. - DON'T (on Windows) run these bash commands in plain PowerShell or
cmd. Open WSL/Ubuntu first, or the commands will fail or behave differently.
Shell
What. The shell is the program running inside the terminal that reads the line you typed, figures out what you meant, and launches the right program to do it. Bash is the shell this bootcamp uses by default.
Why. The terminal is just glass and keyboard, input in, text out. The shell is the brain behind it: it parses your words, finds the program you named, hands it your data, and gives the prompt back when the program is done. Mixing up "terminal" and "shell" is like confusing a TV screen with the channel, one displays, the other decides what plays.
When. Every single time you type a line and press Enter, the shell is the thing interpreting it. You care about which shell you are running because different shells have slightly different syntax, and scripts written for one can misbehave in another.
Recall from earlier in this module that a running program is a process. Here is the key connection: almost every command you type makes the shell start a short-lived process. The shell launches the program and waits while the program writes its own output straight to your terminal, then hands the prompt back to you. One command in, one little process born and gone, prompt returns.
You can ask the shell to tell you its own name. The special word $0 holds the name of the shell currently running.
# Print the name of the shell reading this line.
echo $0-bashecho is a command that prints whatever you give it. $0 is filled in by the shell with its own program name. The leading - is not part of that name, it marks a login shell, which is what WSL opens in a new Ubuntu window. VS Code's built-in terminal on Linux and WSL opens a non-login shell and prints its full path, such as /bin/bash, and a shell you start by typing bash inside another one prints the bare bash. Any of the three confirms you are in the shell this lesson targets. If you see zsh or sh, the commands still mostly work, but bash is the assumed default.
The shell also carries your working directory, Lesson 0.1.4's "here", as part of its own process state, and pwd reads it back:
# Ask the shell which directory it currently treats as "here".
pwd/home/gokuThat is what a new Ubuntu window prints, because it opens in your home directory, while VS Code's built-in terminal opens in the folder VS Code has open and prints /home/goku/dev/bootcamp (/Users/goku/dev/bootcamp on macOS).
DON'T:
- DON'T assume the shell is always bash. On a fresh Mac the default is
zsh. Runecho $0to check before trusting bash-specific behavior. - DON'T expect the shell to remember anything from a closed terminal. Each new terminal starts a fresh shell process with its working directory reset (usually to your home directory).
- DON'T type a command name with a typo and expect it to run. The shell looks for an exact program name,
pdwis notpwd, so nothing runs. Ubuntu answersCommand 'pdw' not found, did you mean:and lists close matches,pwdamong them, but it only suggests, it never runs the guess for you.
Command
What. A command is a single instruction to the shell, specifically, the first word on the line, which names the program you want to run.
Why. The shell reads left to right and treats the very first word as "what program do I launch?" Everything after it is detail. Knowing that the first word is always the verb (the action) is the mental model that makes every command line readable: verb first, then the rest.
When. Every line you type begins with a command. To do anything, list, move, copy, delete, you start by naming the command that performs that action.
A handful of commands cover almost all day-to-day navigation. Each is one verb, one action, one visible result.
pwd # Print working directory - where am I?
ls # List - what is in here?
clear # Clear - wipe the screen, keep the session.
whoami # Who am I logged in as?Each word on its own line is a complete command. Run ls and the shell starts a tiny process that reads the current directory, prints its contents, and exits. Run clear and the screen is wiped (your files are untouched, only the display is cleared).
cd moves the working directory:
cd /home/goku/datasets # Move into the datasets folder Lesson 0.1.4 built.
pwd # Confirm where we landed./home/goku/datasetsNotice cd printed nothing, a silent prompt return means success. Most filesystem commands stay silent when they succeed and only speak up when something goes wrong.
The commands that change the filesystem follow the same verb-first shape:
mkdir models # Make directory named "models".
touch notes.txt # Make an empty file named "notes.txt".Both are silent on success, touch on a file that already exists only updates its timestamp, and an ls afterward shows models and notes.txt in the working directory.
DON'T:
- DON'T put anything before the command. The shell takes the first word as the program name,
please lsmakes it hunt for a program calledplease. - DON'T expect output from commands that work silently.
cd,mkdir, andtouchprint nothing when they succeed, silence is success, not failure. - DON'T forget that
cdwith no argument sends you straight to your home directory. That is occasionally surprising if you meant to type a path and the path got dropped.
Argument
What. An argument is the data a command operates on, the word (or words) you place after the command name to tell it what to act on.
Why. A verb alone is often incomplete. ls lists "here," but ls datasets lists a specific folder. The argument is the target. Without it, commands fall back to a default (usually the working directory), with it, you aim them precisely. In ML work your arguments are constantly file and folder names, train.py, raw_data.csv, models/, so getting arguments right is most of the battle.
When. Use an argument whenever a command needs to know what to act on: which folder to list, which directory to enter, which file to delete.
Arguments are how navigation becomes live. Recall paths from earlier, the text that names a location in the tree. A path handed to a command is just an argument.
ls models # Argument is a RELATIVE path: "models" inside here.
ls /home/goku # Argument is an ABSOLUTE path: start from root.
cd /home/goku/datasets # Argument tells cd exactly where to go.The shorthand path notations you have seen are also just arguments:
cd .. # Argument ".." means "the parent directory", go up one.
cd . # Argument "." means "right here", stays put.
cd ~ # Argument "~" means "my home directory".
cd / # Argument "/" means "the root of the whole tree"... and . are resolved against where you stand, while ~ and / land in the same place from anywhere.
cd ~ # Back home, where the datasets folder lives.
touch raw_data.csv # A stand-in data file for the examples below.
mkdir old_experiments # A stand-in folder, removed later in this lesson.Many commands take two arguments, a source and a destination:
cp raw_data.csv backup.csv # Copy: source first, destination second.
mv raw_data.csv datasets/ # Move raw_data.csv INTO the datasets folder.cp copies raw_data.csv to a new file backup.csv, both files now exist. mv moves raw_data.csv into datasets/, the original is gone from here and now lives there. The order is always source → destination. Swap them and you will copy or move the wrong direction.
And the command that removes takes the target as its argument:
rm datasets/notes.txt # Remove the notes.txt made inside datasets.rm deletes datasets/notes.txt. There is no recycle bin, the argument names a file that is about to vanish permanently.
DON'T:
- DON'T mix up source and destination with
cpandmv. Source is always first, destination second, reversing them can overwrite the wrong file. - DON'T pass a relative path argument and assume it works everywhere.
ls modelsfails the instant your working directory has nomodelsinside it, this is the root of "it worked here but not there." - DON'T hand
rma file you are not certain about. The argument you type is deleted immediately and permanently, there is no undo.
Flag / Option
What. A flag (also called an option) is a switch you add to a command to change how it behaves. Flags almost always begin with a dash: short form -l, or long form --all.
Why. One command often needs several modes. ls lists names, ls -l lists names plus sizes, dates, and permissions, ls -a also shows hidden files. Rather than inventing a new command for every variation, the shell lets one command wear different switches. Flags are how a small set of commands covers a huge range of needs.
When. Reach for a flag whenever the default behavior is close but not quite what you want, more detail, include hidden files, act recursively on a folder, or ask for confirmation before deleting.
The clearest first flags belong to ls.
ls # Plain: just the names in this directory.
ls -l # Long format: permissions, size, and modified date per item.
ls -a # All: include HIDDEN files (names starting with a dot).The -l and -a are flags: same ls command, different behavior.
Flags can be combined into one dash:
ls -la # Both -l and -a at once: detailed AND including hidden files.
ls -al # Same thing, order of combined flags does not matter here.-la is just -l and -a stacked. This is the normal, idiomatic way to write it, one dash, multiple letters. The result is a detailed listing that also includes hidden entries.
Some flags are required to make a command work on folders. By default cp and rm refuse to act on a whole directory, the -r ("recursive") flag tells them to descend into it.
cp -r datasets datasets_backup # Copy an ENTIRE folder and everything inside.
rm -r old_experiments # Remove a folder and ALL its contents.Without -r, cp datasets datasets_backup errors with "omitting directory," because copying a folder means copying every file inside it too, that is what recursive means. rm -r old_experiments deletes the folder and everything beneath it in one stroke.
Two flags are worth knowing for safety and convenience:
touch datasets/notes.txt # Recreate the file removed earlier, so there is one to confirm.
rm -i datasets/notes.txt # Interactive: ask "are you sure?" before deleting.
mkdir -p datasets/2026/raw # Parents: make the whole chain if missing.rm -i makes rm pause and ask for a y before each deletion, a guardrail against the no-undo problem. mkdir -p, met in Lesson 0.1.3, creates only the levels that do not exist yet, here 2026 and then raw inside the datasets folder already there.
DON'T:
- DON'T combine flags that take their own values. Stacking like
-laworks for simple on/off switches, keep a flag that takes a value out of a stack unless you know the command allows it. - DON'T use
rm -rcasually. Recursive removal deletes an entire branch of the tree with no confirmation and no recycle bin, pair it with-iuntil the habit is solid. - DON'T assume every command uses the same flag letters.
-rmeans "recursive" forrmandcp, but flags are defined per command, check before you guess.
Environment Variables and PATH
What. An environment variable is a named value the shell stores and passes into every process it starts. Its name is uppercase by convention and you read it with a $ in front, as in $HOME. PATH is one specific environment variable, holding a colon-separated list of directories, and it is the one that decides which program a bare command name actually runs.
Why. When you type ls you do not type /usr/bin/ls, yet the right program runs. PATH is why. Programs also need configuration they were not handed as arguments, where your home directory is, what your username is, which directory a tool should write to, so rather than every program asking, the shell holds these as named values and gives a copy to each process it launches. That copying is the key idea, a process inherits its environment from the shell that started it.
When. You read a variable whenever a command behaves in a way its arguments do not explain. You set one when many commands need the same value. You touch PATH specifically when a program you installed is not runnable by bare name.
# Read a variable with '$NAME'. These are set for you at login.
echo $HOME # /home/goku
echo $USER # goku
# 'printenv' dumps the WHOLE environment as NAME=value lines.
printenv# A bare assignment is a SHELL variable: only this shell sees it.
MODEL_DIR=/data/models
# 'export' promotes it to an ENVIRONMENT variable, so every process
# this shell starts from now on inherits a copy.
export MODEL_DIR
export DATASET=/data/train.csv # set and export in one lineWithout export, a script you run will not see the value. That single distinction is what separates "I set it" from "the program can read it".
# 'PATH' is a colon-separated list of directories, searched left to right.
echo $PATH
# e.g. /home/goku/dev/bootcamp/.venv/bin:/home/goku/.local/bin:/usr/local/bin:/usr/bin:/bin:/snap/bin
# yours will differ, and that it differs is the point of this whole subtitle
# Extend it by putting '$PATH' back on the right, so the old list survives.
export PATH="$HOME/bin:$PATH" # prepend '~/bin', keeping everything elsePlacement decides ties. A directory earlier in the list is searched first, so prepending makes your own copy of a program win over the system one.
DON'T:
- DON'T forget
export. A bareVAR=valueis local to the current shell, and the scripts and tools you launch will not see it. - DON'T overwrite
PATHwithout including$PATH. Writingexport PATH="$HOME/bin"alone discards every system directory, andls,grepand almost everything else become "command not found". - DON'T put
., the current directory, early inPATH. A file named like a real command, sitting in whatever directory you happen to be standing in, would run instead of the real one.
Executable Lookup and the Profile
What. Executable lookup is what the shell does with PATH: it searches each directory in the list, left to right, and runs the first matching executable it finds. A profile is a startup file in your home directory, ~/.bashrc, that bash reads every time a new interactive shell (one that gives you a prompt) starts, which is how a setting outlives one session. On a Mac running zsh the profile is ~/.zshrc instead.
Why. Lookup explains three everyday mysteries at once, why a command works, why command not found happens when the program is definitely installed, and why two programs with the same name resolve to one specific winner. The profile matters because a variable you set by hand vanishes when the terminal closes, so a setting you always want has to live somewhere the shell re-reads for you.
When. You check lookup whenever a command is missing or the wrong version runs, which is the shape of nearly every environment failure you will meet, wrong python3, a package installed into an interpreter you are not using. You edit the profile when a setting should apply to all future shells rather than this one.
# 'which' shows the full path of the program a bare name resolves to.
which python3 # /home/goku/dev/bootcamp/.venv/bin/python3 with (bootcamp) in the prompt
# /usr/bin/python3 without it
# 'type' is richer, it says HOW a name resolves.
type ls # ls is aliased to `ls --color=auto' on stock Ubuntu and WSL
type cat # cat is /usr/bin/cat in a fresh shell, cat is hashed (/usr/bin/cat) after one run
type cd # cd is a shell builtin, never looked up in PATH at alltype reports three different kinds of thing here, and the differences are the point. cd is a builtin baked into the shell itself, which is why which cd comes up empty, there is no file to find. A builtin runs inside the shell's own process, which is why cd can move the shell at all. cat is an external program the shell locates through PATH. And ls is neither: Ubuntu's stock ~/.bashrc defines alias ls='ls --color=auto', so in any interactive shell the name ls resolves to that alias before PATH is ever consulted. Run type ls expecting a path and the alias line is what you get.
The word hashed is the shell reporting a shortcut rather than a different answer. Once it has searched PATH for a name it remembers where it landed, so the next cat skips the search entirely. You will see the plain form in a shell that has never run the command and the hashed form after it has, and both name the same file.
# When lookup fails, the message names the exact problem.
notarealcommand
# notarealcommand: command not found
# -> no directory in PATH held an executable by that name# The profile is a hidden file, so a plain 'ls' does not show it.
ls -a ~ # .bashrc among the rest
# Make a PATH change permanent by APPENDING the line to the profile.
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
# A profile change reaches NEW shells only. Re-read it into this one,
# stepping out of (bootcamp) first and back in after:
deactivate
source ~/.bashrc
source ~/dev/bootcamp/.venv/bin/activateactivate begins by restoring the PATH it saved when the environment was switched on, so running it while the environment is still active, as it stays through source ~/.bashrc, silently drops the new ~/bin, and stepping out with deactivate first is what keeps it.
That gives three widening scopes for a value, and matching the scope to the need is the whole skill. VAR=x lives in one shell and is not even passed to what it launches. export VAR=x is inherited by every process that shell starts. The same line in the profile applies to every future shell as well.
DON'T:
- DON'T assume
command not foundmeans the program is missing. It is often installed in a directory that is not on yourPATH, so checkwhichandecho $PATHbefore reinstalling anything. - DON'T expect
whichto find a builtin.cdandexportlive inside the shell rather than as files, so usetypeto see a name's true nature. - DON'T write the profile with
>. A single>truncates~/.bashrcto your one line and destroys the setup already in it, always append with>>. - DON'T expect an edited profile to change the shell you are already in. It is read at startup, so the running shell keeps the environment it was born with until you
sourcethe file or open a new terminal.
Lab Exercises
Lab Exercise 1: Open, Identify, Locate
In a fresh terminal: confirm which shell is running you, print where you currently stand in the filesystem, and list what is in your home directory, including hidden files. Use only commands and flags from the first half of this lesson.
Lab Exercise 2: Build a Small Tree
Starting from your home directory, create a project folder named mnist_project containing two sub-folders, datasets and models, and one empty file named README.txt inside mnist_project. Then move into mnist_project and verify the structure. Use arguments and at least one flag. Next, store the folder's full path in a variable named PROJECT_DIR, and show that printenv, a separate program the shell starts, can read it only after you export it. Finish by finding which file the bare name printenv runs.
Solution 1: Open, Identify, Locate
echo $0 # Which shell am I running? Expect -bash, bash or a path ending in /bash.
pwd # Where am I? An absolute path, e.g. /home/goku.
ls -a ~ # List home, INCLUDING hidden dot-files.echo $0 prints the shell's own name so you can confirm you are in bash. pwd prints the absolute path of your working directory. ls -a ~ lists your home directory wherever the terminal opened, and the -a flag makes hidden files (.bashrc, .profile) appear alongside the visible ones.
DON'T:
- DON'T use plain
lshere and conclude the folder is "almost empty." Without-athe hidden configuration files are invisible.
Solution 2: Build a Small Tree
cd ~ # Make sure we start from home.
mkdir mnist_project # Create the top-level project folder.
mkdir mnist_project/datasets # Sub-folder inside it (relative path argument).
mkdir mnist_project/models # Second sub-folder.
touch mnist_project/README.txt # Empty file inside the project.
cd mnist_project # Move into it.
ls -l # Verify: should show datasets, models, README.txt.
PROJECT_DIR=$HOME/mnist_project # A shell variable: only this shell sees it.
printenv PROJECT_DIR # Prints nothing, 'printenv' is a child process and got no copy.
export PROJECT_DIR # Now every process this shell starts inherits a copy.
printenv PROJECT_DIR # /home/goku/mnist_project
which printenv # /usr/bin/printenv, the file the bare name ran.cd ~ guarantees a known starting point. The three mkdir lines build the folder and its two children using relative path arguments. touch creates the empty README.txt. cd mnist_project enters the folder, and ls -l prints a detailed listing confirming all three items exist.
DON'T:
- DON'T create
datasetsandmodelsbeforemnist_projectexists. The parent must exist first, or usemkdir -pto build the chain in one go.
Theory Assessment
Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.
Q1: The window and the program inside it
Explain the difference between the terminal and the shell, and say which one actually interprets the command you type. Then read a prompt: given goku@machine:~$, what does the ~ tell you, and what does the $ mark?
Hint: Think of a TV, where one part displays and the other decides what plays. For the prompt, one piece is a location and one is a boundary for your input.
Q2: Verb, switch, target
Given ls -l datasets, identify the command, the flag and the argument, and state what each one contributes.
Hint: First word, dash-word, then target, in that order.
Q3: The command that is installed and still not found
A package is definitely installed, and typing its command still answers command not found. Explain the mechanism that produces that outcome, and name the two commands you would run before reinstalling anything.
Then a second case: which python3 reports a path, but the interpreter that answers is not the one a colleague gets on the same machine. Nothing is broken and nothing is missing. Explain what differs between the two shells and why the order of one value decides it.
Hint: The shell searches an ordered list and stops at the first hit, so ask what the list holds rather than what is installed. For the second half, two shells can hold different versions of the same list.
Q4: Silence, and how to tell it apart from failure
You run mkdir models and the prompt returns with no message. Did it work, and how would you confirm? Then another version of the same question: you run ls models in one directory and it works, run the identical command elsewhere and get "No such file or directory". Explain why. The shell also stores the last command's result as a number in $?, which echo $? prints, 0 for success and any other number for failure, so explain why that number answers "did that actually work" where silence cannot. Finally, explain why ls -la is an unremarkable combination while rm -ri is a deliberate safety choice.
Hint: Most filesystem commands speak only when they fail, which makes silence ambiguous to a human and precise to the shell. The command never changed between the two directories, your working directory did. For the last part, ask which of the two commands can destroy something.
Answer 1:
The terminal is the window: it captures your keystrokes and displays text. The shell, bash by default, is the program running inside that window, and it reads the line, interprets it, finds the program you named and runs it, and that program writes its result to the window. The shell is the one that interprets, the terminal merely shows.
In the prompt, ~ is your current location, shorthand for your home directory, so the prompt is telling you that "here" is home. The $ marks the boundary where your input begins: everything you type goes to the right of it, and the $ itself is never part of the command.
DON'T call the black window "bash", the window is the terminal and bash runs inside it. DON'T copy the $ when pasting a command from a tutorial.
Answer 2:
ls -l datasets
# ls -> the COMMAND (the verb: "list").
# -l -> the FLAG (changes how: long, detailed format).
# datasets -> the ARGUMENT (what to act on: the datasets folder).ls names the action. -l modifies how it lists, a detailed table instead of bare names. datasets names what to list. Verb, switch, target.
DON'T call -l an argument. A leading dash makes it a flag, a behavior switch, not data to operate on.
Answer 3:
command not found does not mean the program is absent, it means the shell searched every directory in PATH and none of them held an executable by that name. The program can be perfectly installed in a directory that is simply not on the list. The two commands to run first are:
which the-command # empty output confirms the lookup found nothing
echo $PATH # shows which directories were actually searchedIf the program's directory is missing from that list, the fix is to add it rather than to reinstall.
The second case is the same rule seen from the other side. Lookup runs left to right and stops at the first match, so when two interpreters exist in two different directories, the winner is decided by which directory appears earlier in PATH, not by which is newer or better. The two shells hold different PATH values, most often because one has a directory prepended that the other does not, so each stops at a different first match. Nothing is broken, the ordered list is doing exactly what it says.
DON'T treat command not found as evidence the program is missing. DON'T expect which to explain a builtin, cd lives inside the shell and has no file to find, which is what type is for.
Answer 4:
Yes, mkdir models worked. Most filesystem commands are silent on success and speak only when something fails. Confirm with ls and look for models in the output. Do not re-run the mkdir "just to be sure", because the second run errors with "File exists", which can fool you into thinking the first attempt failed.
The ls models case is the same ambiguity one level up. The command did not change, your working directory did. models is a relative path meaning "the models folder inside wherever I currently am". It existed in the first directory and does not in the second. Run pwd before blaming the command.
Silence only implies success, while $? states it for every command: echo $? right after the mkdir prints 0, and right after the two ls runs it prints 0 and then 2. This is the precise answer to a question silence cannot answer, and it is why the same silence that is reassuring to you is unambiguous to a script.
ls -la stacks two on-off switches, -l for the long format and -a for hidden dot-files. Neither takes a value and the command only reads, so combining them carries no risk. rm -ri also stacks two, -r for recursive and -i for interactive confirmation, but that one writes destructively: -r removes an entire branch with no undo, and -i forces a pause so you can stop before destroying the wrong thing.
DON'T read silence as "nothing happened", it is the normal report of success. DON'T drop the -i from rm -r until double-checking your target is automatic.
Assignment
Assignment: Scripted Cleanup with Verification
Build a stand-in experiment in your home directory first, a folder experiment_01 holding results/metrics.csv and config/params.txt. Back up all of experiment_01 to experiment_01_backup, then remove the original results/ folder with confirmation prompting. After the deletion, verify the original results/ is gone and the backup still contains it. Then check the exit status of your last listing command to confirm it ran cleanly. Finally, make a second backup, experiment_01_backup2, in one line that prints backup ok only if the copy succeeded.
Hints:
- Recursive copy first, then recursive-interactive remove, verify with
lson both the original and the backup. $?holds the last command's pass/fail result, and&&runs the next command only if the previous one succeeded.
Solution:
cd ~ # Known starting point.
# --- test setup: build the experiment this assignment operates on ---
mkdir -p experiment_01/results experiment_01/config
echo "run,acc" > experiment_01/results/metrics.csv
echo "lr=0.01" > experiment_01/config/params.txt
cp -r experiment_01 experiment_01_backup # Full recursive backup FIRST.
rm -ri experiment_01/results # Recursive delete WITH confirmation.
ls experiment_01 # Verify results/ is gone here.
ls experiment_01_backup # Verify results/ survives in backup.
echo $? # 0 = last ls succeeded.
# Chain with && so each step runs only if the prior one passed.
cp -r experiment_01 experiment_01_backup2 && echo "backup ok"cp -r makes the full backup before anything destructive happens. rm -ri experiment_01/results removes the folder recursively but pauses for a y/n on each item, three questions here, one to descend into results/, one for metrics.csv inside it, and one for the now-empty directory, guarding against mistakes. The two ls calls confirm results/ is absent from the original but present in the backup. echo $? prints the exit status of the last ls, 0 confirms it ran cleanly. The final line uses &&, which runs echo "backup ok" only if the copy succeeded.
DON'T delete results/ before the backup is made and verified. Order matters: back up, confirm, then remove, never the reverse.
Additional Resources
Optional, and nothing later depends on either. Worth a look only if this lesson left you curious.
- YouTube - "Beginner's Guide to the Bash Terminal" (Joe Collins), the "History" and "Getting Help" chapters from 19:49 to 24:41. Recalling earlier commands with
history, and finding and reading a command's manual withapropos,whatisandman. - GNU Bash Reference Manual - "Bash Startup Files". Which files bash reads as it starts, one set for a login shell and
~/.bashrcfor every other interactive shell, the full rules behind the profile edit in this lesson.
Summary
This lesson was the moment the machine stopped being a wall of buttons and became something you speak to directly. The terminal is only a window for text, and the shell behind it is the patient interpreter that turns nearly every line you type into a short-lived process, born when you press Enter, gone by the time the prompt returns. Everything you did, from pwd to rm -ri, was that same simple loop: name a verb, point it at some data, optionally flip a switch, and read the result.
The structure underneath every command is worth holding onto, because it never changes. The first word is the command, the action. What follows is split into arguments, the what, and flags, the how. ls lists here, ls -l datasets lists a specific folder in detail. Once that shape is automatic, an unfamiliar command stops being intimidating: you can read it left to right and know which part is the verb, which is the target, and which is the modifier, even before you know exactly what it does.
Underneath the typing sits a second invisible input. Every command you launch inherits a set of named values from the shell, and one of them, PATH, is an ordered list of directories the shell walks to find the program behind a bare name. It runs the first match and stops. That single rule is the whole explanation for command not found on something you definitely installed, and for the wrong interpreter answering to python3, and which and type are how you settle either question in one line rather than guessing. A value set by hand lives in one shell, a value you export is inherited by everything that shell starts, and a value in ~/.bashrc reaches every future shell, which is three widening scopes for the same idea.
The quiet lesson threaded through all of it is the working directory. Relative paths, models, .., datasets/notes.txt, mean nothing on their own, they only resolve once the operating system combines them with where you currently stand. That single fact explains the most common early frustration in all of computing: the command that worked here and failed there. Nothing about the command changed. You moved. From here on, when something fails for no apparent reason, your first instinct should be to run pwd and ask not "what is wrong with this command?" but "where am I standing when I run it?"
Cheat Sheet
Part A - Reference Table
| Concept | Syntax / Example | Notes | When to Avoid |
|---|---|---|---|
| Command | ls |
First word = the action/verb | Never put words before it |
| Argument | ls datasets |
The data/target acted on | A relative arg fails if not present here |
| Flag / Option | ls -l, --all |
Switch that changes behavior | Don't combine value-taking flags |
| Print location | pwd |
Shows current working directory | - |
| Change directory | cd path, cd .., cd ~ |
.. up, ~ home, / root |
cd alone jumps home |
| List | ls, ls -a, ls -la |
-a shows hidden dot-files |
Plain ls hides config files |
| Make dir / file | mkdir name, touch file |
mkdir -p a/b/c builds chain |
Parent must exist without -p |
| Copy | cp src dst, cp -r dir dst |
-r needed for folders |
Source first, destination second |
| Move / rename | mv src dst |
Renames if dst is a new name | Original is gone after move |
| Remove | rm file, rm -r dir, rm -ri dir |
-r recursive, -i confirms |
No undo, -r is permanent |
| Which shell | echo $0 |
Confirms bash vs zsh | - |
| Last result | echo $? |
0 = success, non-zero = fail |
- |
| Read a variable | echo $HOME, printenv |
One value, or the whole environment | - |
| Set vs export | VAR=x / export VAR=x |
Bare stays in this shell, export is inherited | Forgetting export before running a script |
| Search list | echo $PATH |
Colon-separated dirs, first match wins | - |
| Extend PATH | export PATH="$HOME/bin:$PATH" |
Prepend, keeping the old list | Omitting $PATH wipes the system dirs |
| Which program runs | which name, type name |
Resolves a bare name, type also spots builtins |
which on a builtin finds nothing |
| Make it permanent | echo '...' >> ~/.bashrc |
Read at every new interactive shell | > truncates the profile |
| Apply it now | deactivate, source ~/.bashrc, source ~/dev/bootcamp/.venv/bin/activate |
Re-reads the profile into this shell, then re-enters (bootcamp) |
Re-activating without deactivate first drops ~/bin |
Part B - Compact Code Block
# --- Where am I / who am I ---
pwd # current working directory
echo $0 # which shell (-bash, bash or a path ending in /bash)
ls -la # list all, detailed, incl. hidden
# --- Navigate ---
cd ~ # home
cd /path/to/dir # absolute path
cd ../datasets # relative: up then into datasets
cd - # back to previous directory
# --- Create ---
mkdir -p ml/data/raw # make whole chain
touch ml/notes.txt # empty file
# --- Copy / move / rename ---
cp file.csv backup.csv # copy file
cp -r ml ml_backup # copy folder (recursive)
mv old.csv data/new.csv # move + rename
# --- Remove (no undo) ---
rm file.txt # delete file
rm -ri old_dir # delete folder, ask first
# --- The environment ---
echo $HOME # read one variable
printenv # read the whole environment
export MODEL_DIR=/data/models # set + export so children inherit it
echo $PATH # the ordered search list
export PATH="$HOME/bin:$PATH" # prepend, keeping the old list
which python3 # which file a bare name resolves to
type cd # cd is a shell builtin
# persist it
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
deactivate # step out of (bootcamp) first
source ~/.bashrc # apply it to THIS shell now
# then back into (bootcamp)
source ~/dev/bootcamp/.venv/bin/activate