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.

Lesson 0012 min read#linux#shell#beginner#filesystem

On this page

  1. Words used in this lesson
  2. What a terminal and a shell really are
  3. How a command line is built
  4. Builtins, programs and type
  5. The map: Linux has one tree
  6. Where am I? pwd, cd and paths
  7. Looking around: ls
  8. Reading files: cat, head, tail, wc, less
  9. Making, copying, moving and deleting
  10. Globs: the shell expands before the command sees it
  11. Quotes: single, double and none
  12. Variables, command substitution and the environment
  13. How the shell finds commands: PATH
  14. Links: two ways to give a file another name
  15. Exit status and chaining commands
  16. Aliases and history
  17. Size and space: du and df
  18. Try it
  19. Interview questions
  20. Key takeaways
  21. 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 named root.)
  • Home directory is your own folder, written ~. For the user ada it is usually /home/ada.
  • Working directory (or current directory) is the folder you are "in" right now. pwd prints 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 (like ls).
  • PATH is a list of folders the shell searches when you type a command name.
  • Glob is a pattern like *.log that the shell expands into matching file names before the command runs.
  • Exit status is a number every command returns when it ends. 0 means 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:

  1. The terminal is the window. It shows text and sends your key presses.
  2. The shell is the program that reads your line and decides what to do with it.
  3. 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:

Shell
echo "$SHELL"ps -p $$ -o comm=bash --version | head -1
TEXT
/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.

TEXT
ls   -l   docs│    │    └── argument: which directory to list│    └─────── option: use the long format└──────────── command: the program to run

Options 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:

Shell
ls --help | head -3
TEXT
Usage: 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:

Shell
help cd | head -4
TEXT
cd: cd [-L|[-P [-e]] [-@]] [dir]    Change the shell working directory.        Change the current directory to DIR.  The default DIR is the value of the

Builtins, 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:

Shell
type cdtype lstype -a echocommand -v grep
TEXT
cd is a shell builtinls is /usr/bin/lsecho is a shell builtinecho is /usr/bin/echoecho is /bin/echo/usr/bin/grep

Look 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:

Shell
ls /
TEXT
binbin.usr-is-mergedbootcontainer_info.jsondevetchomeliblib.usr-is-mergedlib64lost+foundmediamntnixold_rootoptprocrootrunsbinsbin.usr-is-mergedsrvsystmpusrvar

A 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:

FolderWhat lives thereExample you can look at
/bin, /sbin, /libCore programs and libraries (see the note below)ls /usr/bin
/usrMost installed programs, libraries and shared data/usr/bin/ls
/etcConfiguration files for the system, as plain text/etc/os-release
/varData that changes while the system runs: logs, caches, queues/var/log
/homeOne folder per normal user/home/ada
/rootThe home directory of the user named root/root
/tmpTemporary files that anyone may write/tmp
/optExtra, self-contained software/opt/node22 (in my sandbox)
/devDevice files. Hardware and special streams look like files/dev/null
/proc and /sysNot real files. The kernel shows live information here/proc/meminfo
/bootKernel and boot loader filesls /boot
/runRuntime data since boot: process ID files, socketsls /run
/srv, /mnt, /mediaData served by this machine; manual mounts; removable mediararely used by hand

"usr-merged". Look at what ls -ld says about /bin, /lib and /sbin on a current Ubuntu:

Shell
ls -ld /bin /sbin /lib /usr/bin
TEXT
lrwxrwxrwx 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/bin

The 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:

Shell
head -3 /etc/os-releaselsb_release -duname -sr
TEXT
PRETTY_NAME="Ubuntu 24.04.5 LTS"NAME="Ubuntu"VERSION_ID="24.04"Description:	Ubuntu 24.04.5 LTSLinux 6.18.44-fc-v114

uname -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.

Shell
ls /var/log | head
TEXT
READMEalternatives.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:

Shell
head -2 /proc/meminfohead -3 /proc/cpuinfo | cut -c1-60
TEXT
MemTotal:       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:

Shell
ls -l /dev/null /dev/zero | cut -c1-60echo hi > /dev/nullecho "exit=$?"
TEXT
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=0

The 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").

Shell
pwdcd /pwdcd -cd ~pwd
TEXT
/tmp/lab-linux-a/l0//tmp/lab-linux-a/l0/root

Three shortcuts worth learning today:

  • cd alone, or cd ~, goes to your home directory. (~ is your home. For me it is /root. For a user ada it 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:

Shell
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
TEXT
/tmp/lab-linux-a/l0/project/srchello worldhello worldhello worldhello world

All 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).

Shell
lsls -als -l
TEXT
docssrc....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:

TEXT
-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 symlink

You 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:

Shell
ls -lh docs
TEXT
total 4.0K-rw-r--r-- 1 root root 49 Oct 12 03:42 notes.txt

A 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:

Shell
find . | sort
TEXT
../.config./.config/secret.conf./docs./docs/notes.txt./src./src/app.txt./src/empty.txt

We 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

Shell
cat docs/notes.txthead -n 2 docs/notes.txttail -n 2 docs/notes.txtwc docs/notes.txtwc -l docs/notes.txt
TEXT
line oneline twoline threeline fourline fiveline oneline twoline fourline five 5 10 49 docs/notes.txt5 docs/notes.txt
  • cat prints the whole file ("concatenate", because it can join many files).
  • head -n 2 prints the first 2 lines, tail -n 2 the last 2.
  • wc counts: lines, words, bytes. wc -l counts lines only. You will use wc -l all 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:

Shell
file src/app.txtfile /bin/ls | cut -c1-90file /binstat src/app.txt
TEXT
src/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 +0000

ELF 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

Shell
cp docs/notes.txt docs/notes.bakmv docs/notes.bak docs/old.txtls docsrm docs/old.txtls docs
TEXT
notes.txtold.txtnotes.txt
  • cp from to copies. mv from to moves and renames (renaming is just moving to a new name in the same folder).
  • rm file deletes. There is no recycle bin. Gone is gone.
  • mkdir name makes a directory. mkdir -p a/b/c makes the whole chain and does not complain if parts exist.

Directories need extra flags:

Shell
cp -r src src-copyls src-copyrm src-copyecho "exit=$?"rmdir src-copyrm -r src-copymkdir x/y/zmkdir -p a/b/c/d
TEXT
app.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.
Shell
touch a1.log a2.log b1.txtls *.logls a?.logls [ab]1.*echo *.nomatch
TEXT
a1.loga2.loga1.loga2.loga1.logb1.txt*.nomatch

The 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:

Shell
echo *echo .*
TEXT
a a1.log a2.log b1.txt docs src.config

Here 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.

Shell
touch "my file.txt"lsrm my file.txtrm "my file.txt"
TEXT
aa1.loga2.logb1.txtdocsmy file.txtsrcrm: cannot remove 'my': No such file or directoryrm: cannot remove 'file.txt': No such file or directory

Without 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:

Shell
name="Ada"echo "hi $name"echo 'hi $name'echo "hi ${name}s"echo "hi $names|"
TEXT
hi Adahi $namehi Adashi |
  • Double quotes "..." keep the text together but still expand $name and $(command).
  • Single quotes '...' keep everything literally. Nothing expands.
  • ${name} with braces marks where the variable name ends. Without it, $names is 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 $.

Shell
name="Ada"echo "today dir is $(basename "$PWD")"n=$(ls | wc -l)echo "$n entries"
TEXT
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.

Shell
export MYVAR=42bash -c 'echo child sees $MYVAR'plain=7bash -c 'echo child sees [$plain]'
TEXT
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.

Shell
echo "$PATH" | tr ':' '\n'
TEXT
/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:

Shell
printf '#!/bin/sh\necho hi from script\n' > mytool./mytoolchmod +x mytool./mytoolmytoolPATH="$PWD:$PATH" mytool
TEXT
/bin/bash: line 30: ./mytool: Permission deniedhi from script/bin/bash: line 30: mytool: command not foundhi from script

Four lessons in one run:

  1. A new file is not executable yet, so ./mytool fails with "Permission denied". chmod +x fixes it (next lesson).
  2. Typing mytool alone fails with "command not found", because the current folder is not in PATH. That is a safety feature. Otherwise a stray file named ls in a download folder could be run by mistake.
  3. ./mytool works because a path with a slash skips the PATH search.
  4. Putting the folder at the front of PATH for one command makes it findable. You normally add ~/bin or ~/.local/bin to PATH in 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.

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:

Shell
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.txt
TEXT
lrwxrwxrwx 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 worldmore

The -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:

Shell
rm src/app.txtcat hard-appcat link-to-appls -l link-to-app
TEXT
hello worldmorecat: link-to-app: No such file or directorylrwxrwxrwx 1 root root 11 Oct 12 03:42 link-to-app -> src/app.txt

The 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.)

Shell
ls /nopeecho "status=$?"trueecho "status=$?"falseecho "status=$?"
TEXT
ls: cannot access '/nope': No such file or directorystatus=2status=0status=1

true 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:

Shell
mkdir -p build && echo "made"ls /nope 2>/dev/null || echo "fallback"
TEXT
madefallback
  • a && b runs b only if a succeeded.
  • a || b runs b only if a failed.
  • a ; b runs 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:

Shell
shopt -s expand_aliasesalias ll='ls -la'll | head -3type ll
TEXT
total 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 example sudo !! re-runs it with sudo.
  • 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?":

Shell
df -h / | head -2du -sh docs
TEXT
Filesystem      Size  Used Avail Use% Mounted on/dev/vda        252G   34G  5.9G  86% /8.0K	docs

df ("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.

  1. Run type -a echo and type cd. Which of them exist as files? Why must cd be a builtin?
  2. Create notes/2026/jan/ with a single command. Then put a file todo.txt with the text buy milk inside jan.
  3. From inside jan, print the file using a path that starts with ... Then print it with an absolute path.
  4. Run echo *.md in an empty folder. What does it print, and why?
  5. Create a file with a space in its name (touch "my notes.txt"). Delete it with a command that works. Then explain what rm my notes.txt would have tried to do.
  6. Make a symlink shortcut to notes/2026/jan/todo.txt. Then make a hard link copy2 to the same file. Run ls -li and say which names share an inode.
  7. Run ls /nope; echo $? and then ls /nope 2>/dev/null || echo failed.
  8. Find the sizes: run du -sh /etc and df -h /. What does each number mean?

Answers

  1. echo exists both as a builtin and as /usr/bin/echo. cd is only a builtin. A program started by the shell is a child and cannot change its parent's working directory, so cd has to run inside the shell itself.
  2. mkdir -p notes/2026/jan && echo "buy milk" > notes/2026/jan/todo.txt.
  3. cat ../jan/todo.txt or cat todo.txt if you are inside jan; cat ../../2026/jan/todo.txt also works. Absolute: cat /tmp/try0/notes/2026/jan/todo.txt.
  4. It prints *.md. When a glob matches nothing, bash leaves it as plain text (unless nullglob is set).
  5. rm "my notes.txt". Without the quotes, the shell splits it into two words, so rm looks for a file my and a file notes.txt.
  6. ln -s notes/2026/jan/todo.txt shortcut, ln notes/2026/jan/todo.txt copy2. todo.txt and copy2 share an inode (and the link count is 2). The symlink has its own inode.
  7. The first prints the error and 2. The second hides the error and prints failed.
  8. du -sh /etc is 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 PATH in order.
  • Linux has one tree starting at /. /etc is config, /var changes at run time, /home has user folders, /tmp is scratch, /proc and /sys are live kernel views. On a modern distro /bin is a symlink to /usr/bin.
  • Absolute paths start with /. Relative paths start from where you are. . is here, .. is up, ~ is home, and cd - goes back.
  • ls -lah, find, cat, head, tail, wc -l, less, file and stat are your eyes. cp, mv, rm, mkdir -p and ln -s change 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

Share this lessonWhatsAppLinkedInX

Keep learning

Part of the study guide

Related series