Linux & Shell
The Terminal, the Shell and the Linux File System
What the shell is, moving around, the directory layout, absolute and relative paths, reading files, and the commands you will use hourly.
On this page
- Words used in this lesson
- What a terminal and a shell really are
- How a command line is built
- Builtins, programs and type
- The map: Linux has one tree
- Where am I? pwd, cd and paths
- Looking around: ls
- Reading files: cat, head, tail, wc, less
- Making, copying, moving and deleting
- Globs: the shell expands before the command sees it
- Quotes: single, double and none
- Variables, command substitution and the environment
- How the shell finds commands: PATH
- Links: two ways to give a file another name
- Exit status and chaining commands
- Aliases and history
- Size and space: du and df
- Try it
- Interview questions
- Key takeaways
- What's next
In one line: The shell is a program that reads the line you type, finds the program you named, and runs it. Linux keeps everything (files, devices, even running programs) in one tree that starts at /. If you can picture that tree and know twelve small commands, you can move around any Linux server.
Most developers meet Linux through a server, a Docker container, a CI job or the Windows Subsystem for Linux. There is no mouse and no folders window. There is only a prompt and a blinking cursor. That feels hard for a day or two, and then it becomes the fastest way to work.
This lesson is lesson 0 of nine. We do not assume you know anything. We start with "what is a terminal", then walk the file system map, then use the commands you will type every hour: ls, cd, cat, cp, mv, rm, mkdir, find and friends. The next lesson, Users, Permissions and Ownership, explains the -rw-r--r-- strings that you will already see in this lesson.
What I ran / checked. Every command here was run for real in a scratch folder (/tmp/lab-linux-a) on Ubuntu 24.04.5 LTS, kernel 6.18, with GNU bash 5.2.21, GNU coreutils, GNU findutils 4.9.0 and GNU grep 3.11. The output is pasted, not typed from memory. Dates, sizes and inode numbers will differ on your machine. One thing is unusual: my sandbox runs as the user root and has extra folders in PATH (for Node, Go and so on), so your prompt and PATH will look different. Also, man pages are stripped out of this sandbox, so I read command help with help and --help instead. For sources, I read the the-art-of-command-line README (github.com/jlevy/the-art-of-command-line) and the pure-bash-bible README (github.com/dylanaraps/pure-bash-bible) through raw.githubusercontent.com. The GNU manual sites, man7.org, the Filesystem Hierarchy Standard site and Greg's Wiki were blocked by the proxy, so the folder meanings below come from what I could see on the live system plus my own knowledge of the standard, and I checked each one with ls. I did not verify any GitHub star counts, so I do not quote them.
Words used in this lesson
- Terminal is the window (or screen) where you type text and see text. It only shows characters.
- Shell is the program inside the terminal that reads your line and runs commands. The most common one is bash.
- Command is a word you type that tells the shell to run something, such as
ls. - Argument is extra text after the command, such as the folder name in
ls docs. - Option (or flag) is an argument that starts with
-and changes how a command works, such as-l. - Prompt is the text the shell prints before your cursor, so you know it is waiting for you.
- Directory is a folder. Path is the written address of a file or directory, such as
/etc/hosts. - Root directory is
/, the top of the whole tree. (Do not confuse it with the user namedroot.) - Home directory is your own folder, written
~. For the useradait is usually/home/ada. - Working directory (or current directory) is the folder you are "in" right now.
pwdprints it. - Absolute path starts with
/and works from anywhere. Relative path does not start with/and is counted from the working directory. - Builtin is a command that is part of the shell itself (like
cd). An external command is a separate program file (likels). PATHis a list of folders the shell searches when you type a command name.- Glob is a pattern like
*.logthat the shell expands into matching file names before the command runs. - Exit status is a number every command returns when it ends.
0means success. Anything else means some kind of failure. - Symbolic link (symlink) is a small file that points to another path. A hard link is a second name for the same file data.
What a terminal and a shell really are
Three things are often mixed up. Here they are in a line:
- The terminal is the window. It shows text and sends your key presses.
- The shell is the program that reads your line and decides what to do with it.
- A command is usually yet another program that the shell starts for you.
So when you type ls and press Enter, the shell reads ls, looks for a program called ls, starts it, waits for it to finish, and prints a new prompt. That is the whole loop.
You can ask the shell what it is:
echo "$SHELL"ps -p $$ -o comm=bash --version | head -1/bin/bashbashGNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)$SHELL is your login shell (the one set for your account). $$ is the process number of the shell you are in now, and ps -p ... -o comm= prints that process's name. We meet processes properly in lesson 2. On most servers the answer is bash. On a modern Mac it is zsh. Both work almost the same for everything in this series. Bash is the one that is almost always installed, so we use bash.
[!tip] You do not need to memorise Linux. You need to know how to look things up and how to look before you act. Two habits: run ls before you rm, and run pwd when you feel lost.
How a command line is built
A command line is words separated by spaces. The first word is the command. The rest are arguments.
ls -l docs│ │ └── argument: which directory to list│ └─────── option: use the long format└──────────── command: the program to runOptions usually come in two styles. A short one with one dash and one letter (-l), and a long one with two dashes and a word (--help). Short ones can be joined: ls -la means ls -l -a.
Almost every command prints a short help text if you give it --help:
ls --help | head -3Usage: ls [OPTION]... [FILE]...List information about the FILEs (the current directory by default).Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.On a normal machine you can also type man ls to open the manual page (press q to quit, /word to search). In my sandbox the manual pages were removed, and man ls only printed a note saying the system is minimized. If you see that on a Docker image, it is the same cause. For builtins such as cd, use help:
help cd | head -4cd: cd [-L|[-P [-e]] [-@]] [dir] Change the shell working directory. Change the current directory to DIR. The default DIR is the value of theBuiltins, programs and type
Not every command is a separate file. cd must be a builtin. Why? Because a separate program runs as a child and cannot change the directory of the shell that started it. Only the shell itself can change its own working directory.
The command type tells you what a name means:
type cdtype lstype -a echocommand -v grepcd is a shell builtinls is /usr/bin/lsecho is a shell builtinecho is /usr/bin/echoecho is /bin/echo/usr/bin/grepLook at echo. It exists twice: once as a builtin and once as a file. The shell prefers the builtin. type -a shows all the places. This matters later when a script behaves differently on two machines, because the names are the same but the programs are not.
command -v name is the portable way to ask "does this command exist?" inside scripts. It prints the path and returns exit status 0 if found.
The map: Linux has one tree
On Windows you have drives (C:, D:). On Linux there is one tree that starts at /, called the root directory. A second disk or a USB stick does not get a new letter. It is mounted (attached) at some folder inside the same tree. Here is the top of the tree on my test machine:
ls /binbin.usr-is-mergedbootcontainer_info.jsondevetchomeliblib.usr-is-mergedlib64lost+foundmediamntnixold_rootoptprocrootrunsbinsbin.usr-is-mergedsrvsystmpusrvarA few of those are special to my sandbox (container_info.json, nix, old_root). The rest are standard. There is a written standard for this layout, called the Filesystem Hierarchy Standard (FHS). Distros follow it closely but not perfectly. Here is what the main folders are for:
| Folder | What lives there | Example you can look at |
|---|---|---|
/bin, /sbin, /lib | Core programs and libraries (see the note below) | ls /usr/bin |
/usr | Most installed programs, libraries and shared data | /usr/bin/ls |
/etc | Configuration files for the system, as plain text | /etc/os-release |
/var | Data that changes while the system runs: logs, caches, queues | /var/log |
/home | One folder per normal user | /home/ada |
/root | The home directory of the user named root | /root |
/tmp | Temporary files that anyone may write | /tmp |
/opt | Extra, self-contained software | /opt/node22 (in my sandbox) |
/dev | Device files. Hardware and special streams look like files | /dev/null |
/proc and /sys | Not real files. The kernel shows live information here | /proc/meminfo |
/boot | Kernel and boot loader files | ls /boot |
/run | Runtime data since boot: process ID files, sockets | ls /run |
/srv, /mnt, /media | Data served by this machine; manual mounts; removable media | rarely used by hand |
"usr-merged". Look at what ls -ld says about /bin, /lib and /sbin on a current Ubuntu:
ls -ld /bin /sbin /lib /usr/binlrwxrwxrwx 1 root root 7 Apr 22 2024 /bin -> usr/binlrwxrwxrwx 1 root root 7 Apr 22 2024 /lib -> usr/liblrwxrwxrwx 1 root root 8 Apr 22 2024 /sbin -> usr/sbindrwxr-xr-x 2 root root 24576 Oct 12 03:42 /usr/binThe l at the start and the -> arrow say these are symbolic links. /bin is just another name for /usr/bin. This is called a "merged /usr" layout and most big distros use it now. Older books say /bin holds the essential programs and /usr/bin holds the rest. That used to be true. Now it is one place with two names.
Look inside a few of them
/etc is plain text, so you can read it. This file says which Linux you have:
head -3 /etc/os-releaselsb_release -duname -srPRETTY_NAME="Ubuntu 24.04.5 LTS"NAME="Ubuntu"VERSION_ID="24.04"Description: Ubuntu 24.04.5 LTSLinux 6.18.44-fc-v114uname -sr prints the kernel name and release. The kernel is the core of the operating system. "Linux" strictly means only the kernel. Ubuntu, Debian and Fedora are distributions that bundle the kernel with the shell, tools and a package manager.
/var/log is where services write their logs. We will use it a lot in lesson 7.
ls /var/log | headREADMEalternatives.logaptbootstrap.logbtmpdpkg.logfaillogfontconfig.logjournallastlog/proc is the strangest. These files do not exist on a disk. The kernel makes them up when you read them:
head -2 /proc/meminfohead -3 /proc/cpuinfo | cut -c1-60MemTotal: 16479424 kBMemFree: 6702208 kBprocessor : 0vendor_id : GenuineIntelcpu family : 6"Everything is a file" is the famous Linux idea. It means that the same simple tools (cat, head, grep) can read normal files, the kernel's live state, and devices. Look at /dev/null, a device that throws away anything you write to it:
ls -l /dev/null /dev/zero | cut -c1-60echo hi > /dev/nullecho "exit=$?"crw-rw-rw- 1 root root 1, 3 Oct 9 23:24 /dev/nullcrw-rw-rw- 1 root root 1, 5 Oct 9 23:24 /dev/zeroexit=0The first letter c means a character device. Writing to /dev/null succeeds and keeps nothing. We use it in lesson 3 to hide noise.
Where am I? pwd, cd and paths
The working directory is where relative paths start. Print it with pwd ("print working directory"). Change it with cd ("change directory").
pwdcd /pwdcd -cd ~pwd/tmp/lab-linux-a/l0//tmp/lab-linux-a/l0/rootThree shortcuts worth learning today:
cdalone, orcd ~, goes to your home directory. (~is your home. For me it is/root. For a useradait would be/home/ada.)cd -goes back to the previous directory and prints where it went. This is the "back button".cd ..goes up one level. Every directory has two hidden entries:.(itself) and..(its parent).
Absolute and relative paths
An absolute path starts with /. It means the same thing from anywhere. A relative path does not start with /. It is counted from where you are now.
I made a small project to test this:
mkdir -p project/src project/docs project/.configcd projectecho "hello world" > src/app.txtcd srcpwdcat app.txtcat ./app.txtcat ../src/app.txtcat /tmp/lab-linux-a/l0/project/src/app.txt/tmp/lab-linux-a/l0/project/srchello worldhello worldhello worldhello worldAll four cat commands name the same file. app.txt and ./app.txt are the same thing ("here"). ../src/app.txt goes up to project, then down into src again. The last one is absolute.
When should you use which? In a script that must work from any folder, use absolute paths (or build them from a variable). When you work by hand, relative paths are shorter.
Relative paths are a classic bug source. A script that says rm -rf build/ deletes build/ in whatever folder you happened to be in. Two lines earlier, a failed cd may have left you in the wrong folder. We fix this properly in lesson 4.
Looking around: ls
ls lists a directory. By default it hides names that start with a dot (called hidden files, mostly config files).
lsls -als -ldocssrc....configdocssrctotal 8drwxr-xr-x 2 root root 4096 Oct 12 03:42 docsdrwxr-xr-x 2 root root 4096 Oct 12 03:42 src-a shows all, including the hidden .config. -l is the long format. Let's read one line of it:
-rw-r--r-- 1 root root 12 Oct 12 03:42 app.txt│└──┬───┘ │ │ │ │ └── modified time └── name│ │ │ │ │ └── size in bytes│ │ │ │ └── group owner│ │ │ └── user owner│ │ └── number of hard links│ └── permissions (next lesson)└── type: - file, d directory, l symlinkYou will see this format everywhere. The permissions part is the topic of the next lesson. For now, remember that the first character is the type.
Add -h (human) to show sizes like 4.0K and 8.8M:
ls -lh docstotal 4.0K-rw-r--r-- 1 root root 49 Oct 12 03:42 notes.txtA very common set is ls -lah (long, all, human sizes).
find shows a whole tree
ls shows one folder. find walks down through all of them:
find . | sort../.config./.config/secret.conf./docs./docs/notes.txt./src./src/app.txt./src/empty.txtWe piped into sort only to keep the order stable. find . -type f lists only files (-type d for directories). You can also test by name: find . -name '*.txt'. Notice the quotes. We will return to this in a moment, and find gets its own deep dive in lesson 3.
Reading files: cat, head, tail, wc, less
cat docs/notes.txthead -n 2 docs/notes.txttail -n 2 docs/notes.txtwc docs/notes.txtwc -l docs/notes.txtline oneline twoline threeline fourline fiveline oneline twoline fourline five 5 10 49 docs/notes.txt5 docs/notes.txtcatprints the whole file ("concatenate", because it can join many files).head -n 2prints the first 2 lines,tail -n 2the last 2.wccounts: lines, words, bytes.wc -lcounts lines only. You will usewc -lall the time.
For a long file, use less. It shows one screen at a time. Press space for the next page, /word to search, n for the next match, and q to quit. less is interactive, so I cannot paste a run of it. I only checked that it exists (less 590). A related trick is tail -f file, which keeps printing new lines as they are added. That is how people watch a log live. Press Ctrl-C to stop.
[!tip] cat is for small files. If you cat a 2 GB log, your terminal will scroll for minutes. Use head, tail or less for anything big.
The command file tells you what a file really is, regardless of its name. stat shows its details:
file src/app.txtfile /bin/ls | cut -c1-90file /binstat src/app.txtsrc/app.txt: ASCII text/bin/ls: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, inte/bin: symbolic link to usr/bin File: src/app.txt Size: 12 Blocks: 8 IO Block: 4096 regular fileDevice: 254,0 Inode: 2588979 Links: 1Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)Access: 2026-10-12 03:42:13.154344209 +0000Modify: 2026-10-12 03:42:13.132766576 +0000Change: 2026-10-12 03:42:13.132766576 +0000 Birth: 2026-10-12 03:42:13.129014033 +0000ELF is the program format on Linux. stat shows three times: Access (last read), Modify (content last changed), and Change (the file's details, like permissions, last changed). The inode is the number the file system uses to find the file. It will matter in a minute.
Making, copying, moving and deleting
cp docs/notes.txt docs/notes.bakmv docs/notes.bak docs/old.txtls docsrm docs/old.txtls docsnotes.txtold.txtnotes.txtcp from tocopies.mv from tomoves and renames (renaming is just moving to a new name in the same folder).rm filedeletes. There is no recycle bin. Gone is gone.mkdir namemakes a directory.mkdir -p a/b/cmakes the whole chain and does not complain if parts exist.
Directories need extra flags:
cp -r src src-copyls src-copyrm src-copyecho "exit=$?"rmdir src-copyrm -r src-copymkdir x/y/zmkdir -p a/b/c/dapp.txtempty.txtrm: cannot remove 'src-copy': Is a directoryexit=1rmdir: failed to remove 'src-copy': Directory not emptymkdir: cannot create directory 'x/y/z': No such file or directory(The last mkdir -p printed nothing, which in Unix means success. Quiet is good.)
cp -r copies a directory and everything in it ("recursive"). rm refuses a directory unless you add -r. rmdir removes only empty directories, which makes it a safe tool.
rm -rf is the sharp knife. -r means recurse, -f means "force: do not ask and ignore errors". There is no undo. A stray space, such as rm -rf /tmp/build / instead of rm -rf /tmp/build/, can be a disaster. Modern GNU rm refuses to delete / itself unless you add --no-preserve-root, but it will gladly delete your whole home folder. Habit: first run ls or echo with the same pattern, check the output, and only then use rm. I only ran rm inside my scratch folder.
Globs: the shell expands * before the command sees it
When you type ls *.log, the shell replaces *.log with the list of matching names before ls starts. ls never sees the star. The common glob characters:
*matches any number of characters (including none).?matches exactly one character.[ab]matches one character from the set.
touch a1.log a2.log b1.txtls *.logls a?.logls [ab]1.*echo *.nomatcha1.loga2.loga1.loga2.loga1.logb1.txt*.nomatchThe last line is a trap. When nothing matches, bash by default leaves the pattern as plain text. So echo *.nomatch prints *.nomatch, and rm *.nomatch would try to delete a file literally called *.nomatch. (You can change that with shopt -s nullglob, which makes a failed pattern expand to nothing. We use it in scripts.)
Globs also skip hidden files:
echo *echo .*a a1.log a2.log b1.txt docs src.configHere is a detail that surprised me. On older bash versions echo .* also printed . and .., which made rm -r .* dangerous. In bash 5.2 the option globskipdots is on by default, so . and .. are skipped (shopt globskipdots prints on). Do not depend on this on an older server. Check your version.
Quotes: single, double and none
Spaces split words. Quotes stop that. This is one of the most important ideas in shell work.
touch "my file.txt"lsrm my file.txtrm "my file.txt"aa1.loga2.logb1.txtdocsmy file.txtsrcrm: cannot remove 'my': No such file or directoryrm: cannot remove 'file.txt': No such file or directoryWithout quotes, the shell split my file.txt into two arguments, my and file.txt, and rm looked for two files that do not exist. With double quotes the name stayed as one word and rm removed it.
The two kinds of quotes behave differently:
name="Ada"echo "hi $name"echo 'hi $name'echo "hi ${name}s"echo "hi $names|"hi Adahi $namehi Adashi |- Double quotes
"..."keep the text together but still expand$nameand$(command). - Single quotes
'...'keep everything literally. Nothing expands. ${name}with braces marks where the variable name ends. Without it,$namesis a different (empty) variable, as the last line shows. That is a silent bug, and no error is printed.
[!tip] A rule that saves you for years: always put double quotes around variables: "$file", not $file. We cover the reasons in lesson 4.
Variables, command substitution and the environment
A variable is a name that holds text. You set it without spaces around =, and read it with $.
name="Ada"echo "today dir is $(basename "$PWD")"n=$(ls | wc -l)echo "$n entries"today dir is project9 entries$(command) is command substitution: the shell runs the command and puts its output in place. The old style is backticks, `command`, but $( ) nests better.
Plain variables belong to the current shell only. To pass a variable to the programs you start, export it. Those are called environment variables. A child process gets a copy.
export MYVAR=42bash -c 'echo child sees $MYVAR'plain=7bash -c 'echo child sees [$plain]'child sees 42child sees []The exported variable reached the child. The plain one did not. PATH, HOME and USER are environment variables that nearly every tool reads.
How the shell finds commands: PATH
When you type grep, the shell does not search the whole disk. It reads PATH, a list of folders separated by colons, and checks each one in order. The first match wins.
echo "$PATH" | tr ':' '\n'/nix/var/nix/profiles/default/bin/root/.local/bin/root/.cargo/bin/usr/local/go/bin/opt/node22/bin/opt/maven/bin/opt/gradle/bin/opt/rbenv/bin/root/.bun/bin/usr/local/sbin/usr/local/bin/usr/sbin/usr/bin/sbin/bin(This is my sandbox, yours will be shorter.) The tr ':' '\n' part turns colons into new lines so the list is easy to read.
Now make your own tool and see why ./ matters:
printf '#!/bin/sh\necho hi from script\n' > mytool./mytoolchmod +x mytool./mytoolmytoolPATH="$PWD:$PATH" mytool/bin/bash: line 30: ./mytool: Permission deniedhi from script/bin/bash: line 30: mytool: command not foundhi from scriptFour lessons in one run:
- A new file is not executable yet, so
./mytoolfails with "Permission denied".chmod +xfixes it (next lesson). - Typing
mytoolalone fails with "command not found", because the current folder is not inPATH. That is a safety feature. Otherwise a stray file namedlsin a download folder could be run by mistake. ./mytoolworks because a path with a slash skips thePATHsearch.- Putting the folder at the front of
PATHfor one command makes it findable. You normally add~/binor~/.local/bintoPATHin your shell startup file.
which -a ls and type -a ls show every match in PATH order. Here ls is found in both /usr/bin/ls and /bin/ls, which are the same file through the merged-usr symlink.
The first line of the file, #!/bin/sh, is called a shebang. It tells the kernel which program should run the file. Lesson 4 is about scripts.
Links: two ways to give a file another name
A symbolic link (symlink) is a small file that holds a path. A hard link is a second directory entry for the same file data. Let me show the difference with a test:
ln -s src/app.txt link-to-appls -l link-to-appcat link-to-appln src/app.txt hard-appls -li src/app.txt hard-appecho "more" >> hard-appcat src/app.txtlrwxrwxrwx 1 root root 11 Oct 12 03:42 link-to-app -> src/app.txthello world2588979 -rw-r--r-- 2 root root 12 Oct 12 03:42 hard-app2588979 -rw-r--r-- 2 root root 12 Oct 12 03:42 src/app.txthello worldmoreThe -i flag shows the inode number. Both names have the same inode, 2588979, and the link count in the second column is now 2. They are not copies. They are the same file with two names. So when I appended a line through hard-app, src/app.txt changed too.
Now delete the original name:
rm src/app.txtcat hard-appcat link-to-appls -l link-to-apphello worldmorecat: link-to-app: No such file or directorylrwxrwxrwx 1 root root 11 Oct 12 03:42 link-to-app -> src/app.txtThe hard link still works, because the data stays until every name is gone. The symlink became a dangling link: it still holds the text src/app.txt, but nothing is there. ls -l still shows it, cat fails.
A symlink can point to a folder or to another disk. A hard link cannot do either. In daily work you use symlinks 95% of the time. Examples: /bin -> usr/bin, and deploy tools that point current -> releases/2026-10-12.
Exit status and chaining commands
Every command returns a number when it finishes. The variable $? holds the last one. 0 means success. Anything else means failure. (This is the opposite of most programming languages, where 0 is false.)
ls /nopeecho "status=$?"trueecho "status=$?"falseecho "status=$?"ls: cannot access '/nope': No such file or directorystatus=2status=0status=1true and false are tiny commands that do nothing except return 0 and 1. Different failures can give different numbers: ls uses 2 for "serious trouble".
You can chain commands using the exit status:
mkdir -p build && echo "made"ls /nope 2>/dev/null || echo "fallback"madefallbacka && brunsbonly ifasucceeded.a || brunsbonly ifafailed.a ; bruns both, no matter what.
(2>/dev/null throws away the error message. More on it in lesson 3.)
Aliases and history
An alias is a short name for a longer command. People use it for habits like ll:
shopt -s expand_aliasesalias ll='ls -la'll | head -3type lltotal 36drwxr-xr-x 7 root root 4096 Oct 12 03:42 .drwxr-xr-x 3 root root 4096 Oct 12 03:42 ..ll is aliased to `ls -la'(The shopt -s expand_aliases line is needed only because I ran this from a script. In an interactive shell, aliases just work.) Put aliases in ~/.bashrc so they load every time you open a terminal. Aliases do not carry into scripts, which is a good thing.
History is the shell remembering what you typed. By default bash keeps 500 lines in memory, which I confirmed with echo $HISTSIZE. The keys that save real time, from the Art of Command Line list:
- Up arrow or Ctrl-P: previous command.
- Ctrl-R: search backwards in history. Type a few letters, press Ctrl-R again for older matches, Enter to run.
- Tab: complete a file or command name. Press it twice to list choices.
- Ctrl-A / Ctrl-E: go to start / end of the line. Ctrl-W: delete the last word. Ctrl-U: delete back to the start.
!!: the last command, for examplesudo !!re-runs it withsudo.- Ctrl-C: stop the running command. Ctrl-D: end of input, exits the shell if the line is empty.
- Ctrl-L or
clear: clean the screen.
I could not paste these because they need a live keyboard, but they are plain bash (readline) features and are worth practicing now.
Size and space: du and df
Two commands answer "where did my disk go?":
df -h / | head -2du -sh docsFilesystem Size Used Avail Use% Mounted on/dev/vda 252G 34G 5.9G 86% /8.0K docsdf ("disk free") shows each mounted file system. du ("disk usage") sums the size of a folder, and -s gives one total and -h makes it readable. Lesson 7 uses both to hunt a full disk.
Try it
Do these in a fresh scratch folder, such as mkdir -p /tmp/try0 && cd /tmp/try0.
- Run
type -a echoandtype cd. Which of them exist as files? Why mustcdbe a builtin? - Create
notes/2026/jan/with a single command. Then put a filetodo.txtwith the textbuy milkinsidejan. - From inside
jan, print the file using a path that starts with... Then print it with an absolute path. - Run
echo *.mdin an empty folder. What does it print, and why? - Create a file with a space in its name (
touch "my notes.txt"). Delete it with a command that works. Then explain whatrm my notes.txtwould have tried to do. - Make a symlink
shortcuttonotes/2026/jan/todo.txt. Then make a hard linkcopy2to the same file. Runls -liand say which names share an inode. - Run
ls /nope; echo $?and thenls /nope 2>/dev/null || echo failed. - Find the sizes: run
du -sh /etcanddf -h /. What does each number mean?
Answers
echoexists both as a builtin and as/usr/bin/echo.cdis only a builtin. A program started by the shell is a child and cannot change its parent's working directory, socdhas to run inside the shell itself.mkdir -p notes/2026/jan && echo "buy milk" > notes/2026/jan/todo.txt.cat ../jan/todo.txtorcat todo.txtif you are insidejan;cat ../../2026/jan/todo.txtalso works. Absolute:cat /tmp/try0/notes/2026/jan/todo.txt.- It prints
*.md. When a glob matches nothing, bash leaves it as plain text (unlessnullglobis set). rm "my notes.txt". Without the quotes, the shell splits it into two words, sormlooks for a filemyand a filenotes.txt.ln -s notes/2026/jan/todo.txt shortcut,ln notes/2026/jan/todo.txt copy2.todo.txtandcopy2share an inode (and the link count is 2). The symlink has its own inode.- The first prints the error and
2. The second hides the error and printsfailed. du -sh /etcis the total size of all files under/etc(mine was 8.8M).df -h /shows the size, used and free space of the file system mounted at/(mine was 252G, 34G used, 5.9G free, so 86% full).
Interview questions
What is the difference between a terminal, a shell and a command? (Must know)
The terminal is the window that shows text and sends my keys. The shell is the program inside it that reads my line, expands things like variables and globs, and starts programs. A command is the thing I ask it to run. It is either a shell builtin, like cd, or an external program found through PATH. In practice, I can change shells (bash, zsh) without changing the terminal, and the same shell can run in a web console, an SSH session or a CI job.
What is the difference between an absolute and a relative path? When would you use each? (Must know)
An absolute path starts with / and means the same thing from every folder. A relative path is counted from the current working directory. In scripts that run from cron or CI, I prefer absolute paths, or I build them from a variable such as "$HOME" or the script's own location, so the result does not depend on where the process started. Interactively, relative paths are faster to type. If I am unsure, I run pwd first.
Why does cd have to be a builtin? (Senior signal)
A child process cannot change the working directory of its parent. If cd were an external program, it would change its own directory and then exit, and my shell would stay where it was. So cd runs inside the shell. The same reasoning applies to export and exit. I can see this with type cd, which says "shell builtin".
What does "everything is a file" mean in Linux? (Must know)
It means that many things are exposed through the file system, so the same tools work on them. Normal files, directories, devices like /dev/null, and kernel information like /proc/meminfo all can be opened and read with the usual calls. For example, I can run head -2 /proc/meminfo to read memory info, or echo hi > /dev/null to discard data. It is not 100% literal (network cards, for example, have no device file), but it is a useful rule that explains why cat, grep and redirection work on so much.
What lives in /etc, /var, /tmp, /home and /proc? (Must know)
/etc holds system configuration in text files. /var holds data that changes at run time, like logs in /var/log, caches and queues. /tmp is a shared scratch area anyone can write to, and it may be cleaned on reboot. /home has one folder per normal user, and /root is the home of the root user. /proc is a virtual folder where the kernel shows live process and system information, nothing there is stored on disk.
What is the difference between a hard link and a symbolic link? (Senior signal)
A hard link is another name for the same inode, so both names are equally "the file". The data is freed only when the link count reaches 0. A symlink is a separate small file that stores a path, so it breaks (dangles) if the target moves or is deleted. Symlinks can point to directories and across file systems, hard links cannot. I showed this with ls -li: the hard link shared inode 2588979 with the original and survived rm of the original name, while the symlink became a dangling link.
You type rm .log in a folder with no log files. What happens, and what if there is a file literally named .log? (Trap)
By default bash leaves a glob that matches nothing as plain text, so rm receives the literal string *.log. If no such file exists, rm prints "No such file or directory". If a file with that exact name does exist, rm deletes it. That is why scripts often enable shopt -s nullglob (an unmatched pattern vanishes) or check that files exist first. The deeper point: the shell expands globs, not the command.
Why is rm my file.txt wrong, and what is the fix? (Must know)
The shell splits on spaces without quotes, so rm gets two arguments, my and file.txt, and neither exists. The fix is quotes: rm "my file.txt". For variables it is the same: write "$file". Unquoted variables with spaces are one of the most common causes of shell bugs, and with rm they can delete the wrong thing.
What does an exit status of 0 mean, and how do && and || use it? (Must know)
Exit status 0 means success, and any non-zero number means some kind of failure. It is stored in $?. a && b runs b only if a returned 0. a || b runs b only if a failed. a ; b runs both always. I use them for short guards like mkdir -p build && cd build. This is the reverse of C-style truthiness, where 0 is false, which trips up some developers.
A script works when you run ./deploy.sh but fails from cron with "command not found". What do you check? (Senior signal)
I check PATH first. Cron starts commands with a very small environment, so folders I normally have in PATH (like /usr/local/bin or ~/.nvm/...) may be missing, and the working directory is also different. The fixes are to use absolute paths to programs, or set PATH at the top of the script, and not to depend on aliases or on being in a specific folder. I debug it by printing echo "$PATH" and pwd from inside the cron job.
How do you find out where a command comes from and which version runs? (Must know)
I use type -a name which lists every match in PATH order and says if it is an alias, builtin or file, and command -v name in scripts. which -a name is similar for files. If two versions are installed (for example two python3 or node), the first folder in PATH wins. This is how I debug "it works on my machine" problems.
What is the danger of rm -rf and how do you reduce it? (Senior signal)
-r recurses and -f removes without asking and hides most errors, and there is no trash can. The risk is an unexpected path: an empty variable, a stray space, or a failed cd that left me in the wrong folder. I reduce it by first running ls or echo with the same arguments, by quoting variables, by using rm -r without -f when possible, by using rmdir for empty directories, and in scripts by using "${dir:?}" so an empty variable stops the script. For anything important, I rely on backups, not on being careful.
Key takeaways
- The terminal shows text, the shell reads your line, and commands are builtins or programs found by searching
PATHin order. - Linux has one tree starting at
/./etcis config,/varchanges at run time,/homehas user folders,/tmpis scratch,/procand/sysare live kernel views. On a modern distro/binis a symlink to/usr/bin. - Absolute paths start with
/. Relative paths start from where you are..is here,..is up,~is home, andcd -goes back. ls -lah,find,cat,head,tail,wc -l,less,fileandstatare your eyes.cp,mv,rm,mkdir -pandln -schange things. Look before you delete.- The shell expands globs and variables before the command runs. Quote variables and names with spaces. A glob with no match stays as plain text.
- Exit status 0 means success.
&&and||act on it. - A hard link is a second name for the same inode. A symlink is a file holding a path, and it can dangle.
What's next
Next: Users, Permissions and Ownership, Lesson 1. You will learn to read the -rw-r--r-- part of ls -l, change it with chmod and chown, understand sudo, umask and the setuid bit, and see the permission mistakes that cause "Permission denied" at 2 a.m. Practise now: make a folder with five files in your scratch space, and describe each one with ls -l and stat out loud.
Published by Jainik · Last updated · How these lessons are made
Keep learning
Part of the study guide
Related series