Table of contents
- Why You Should Start with the Shell
- The Shell as a Translator
- The Filesystem Is One Giant Tree
- pwd, cd, ls — Three Ways to Walk
- Absolute and Relative Paths
- cat, less, head, tail — Peeking Inside Files
- $PATH — How Commands Are Found
- Environment Variables — The Shell’s Memory
- Shell Configuration Files — Executed on Every Login
- Peeking into the Kernel via /proc
- What We Covered in This Part
Why You Should Start with the Shell
When a developer first SSHs into an EC2 instance, all they see is a black screen and a single $ sign. There are no buttons to click, no IDE autocompletion. That moment of hesitation — “what do I type first?” — is the first real hurdle in practice.
Learning Linux is the process of getting comfortable with this black screen. At the center of it all is the shell. The shell is a translator that interprets the strings you type and tells the operating system what to do — the entry point for all Linux operations. Whether you’re viewing Kubernetes Pod logs, spinning up Docker containers, or running CI/CD pipelines, shell commands are being executed somewhere under the hood.
In this part, we’ll walk through what the shell is, how the directory structure is organized, why environment variables like $PATH matter — peeling back the layers behind the basic commands you use every day.
The Shell as a Translator
Linux places a shell between the kernel and the user. It receives text typed on the keyboard, interprets it, and translates it into system calls that the kernel can understand.
flowchart LR
USER["User<br/>(keyboard)"] --> TERM["Terminal<br/>emulator"]
TERM --> SHELL["Shell<br/>(bash / zsh / fish)"]
SHELL --> KERN["Linux Kernel"]
KERN --> HW["Hardware<br/>(CPU / disk / network)"]
KERN --> SHELL
SHELL --> TERM
TERM --> USER
The most well-known shell is bash (Bourne Again SHell). Since its release in 1989, it has held the default shell position on nearly every distribution, and it remains the default in server environments today. macOS switched its default shell to zsh in 2019. Functionally, both are similar — the difference boils down to whether your prompt looks prettier with stronger autocompletion, or whether you prefer something lightweight that’s available everywhere.
Checking which shell you’re using is straightforward.
echo $SHELL
# /bin/zsh (or /bin/bash)
echo $0
# -zsh
$SHELL holds the path to the shell that opens by default at login, and $0 returns the name of the currently running shell. The two values can differ — for example, if a zsh user launches a subshell with bash script.sh, $SHELL remains zsh while $0 becomes bash.
This series is written based on bash, but only uses commands that work 99% identically in zsh.
The Filesystem Is One Giant Tree
Windows has multiple drive letters — C:, D:, E:. Linux is different. Everything starts from a single / (root) and branches downward. Even when you plug in a USB drive or add a new disk, it gets “mounted” to some directory and becomes part of the tree. This is the first face of the Unix philosophy: “Everything is a file.”
The directory hierarchy that Linux follows is defined by a standard called FHS (Filesystem Hierarchy Standard). You can find the detailed specification in the Linux Foundation’s FHS document.
flowchart TB
ROOT["/ (root)"]
ROOT --> BIN["/bin<br/>Essential commands"]
ROOT --> SBIN["/sbin<br/>Admin commands"]
ROOT --> ETC["/etc<br/>System configuration"]
ROOT --> HOME["/home<br/>User home directories"]
ROOT --> VAR["/var<br/>Variable data (logs, caches)"]
ROOT --> TMP["/tmp<br/>Temporary files"]
ROOT --> USR["/usr<br/>User programs"]
ROOT --> PROC["/proc<br/>Kernel/process info"]
ROOT --> SYS["/sys<br/>Kernel/device info"]
ROOT --> DEV["/dev<br/>Device files"]
ROOT --> OPT["/opt<br/>3rd-party"]
Let’s look at what kind of files each directory holds.
/bin,/sbin: The most essential executables. Commands likels,cp,mvgo in/bin, while admin commands likeshutdownandiptablesgo in/sbin. Modern distributions tend to merge these into symlinks pointing to/usr/binand/usr/sbin/etc: Configuration files for the system and services. SSH config (/etc/ssh/sshd_config), nginx config (/etc/nginx/nginx.conf) — they all live here. “Configuration lives in /etc” is an unwritten rule of Linux/home: Home directories for regular users. Useralice’s home would be/home/alice. You can quickly navigate there withcd ~/var: “Variable data” goes here. Logs (/var/log/), mail spools (/var/mail/), caches (/var/cache/), database files (/var/lib/postgresql/), etc. Most things whose size changes continuously during operation live here/tmp: Temporary files. By convention, this is cleared on reboot. It often has size limits, so it’s best not to store large files here/usr: “User-related resources.” Despite the name, this doesn’t hold user data — it contains programs, libraries, and shared resources used by the entire system./usr/bin,/usr/lib,/usr/share/doc, etc./proc: Not actual files — it’s a virtual filesystem generated by the kernel. Opening/proc/cpuinfoshows CPU information,/proc/meminfoshows memory status, and/proc/<PID>/statusshows information about a specific process, all as text/sys: A virtual filesystem similar to/proc, but it exposes hardware and kernel objects. This is why Docker writes directly to files under/sys/fs/cgroup/when manipulating cgroups/dev: Device files./dev/sdais the first SATA disk,/dev/nullis a “black hole,” and/dev/randomis a random number generator. The “everything is a file” philosophy is on full display here too
In one line: Configuration in /etc, logs and data in /var, commands in /bin, personal files in /home. Memorize these four and you’ll cover 80% of daily operations.
pwd, cd, ls — Three Ways to Walk
Getting comfortable with the shell ultimately comes down to quickly figuring out “where am I, where am I going, and what’s here.” Three commands serve this purpose.
# Where am I right now (Print Working Directory)
pwd
# /home/alice
# Navigate somewhere (Change Directory)
cd /var/log
cd ~ # Go to home directory
cd - # Go to the previous directory
# What's here (LiSt)
ls
ls -l # Detailed info (long)
ls -la # Include hidden files (all)
ls -lh # Human-readable sizes (human)
You’ll see ls -la output every day. Dissecting it from left to right will make later chapters easier.
-rw-r--r-- 1 alice staff 1024 Apr 20 10:30 .bashrc
drwxr-xr-x 3 alice staff 96 Apr 20 09:15 projects
- First character: file type.
-is a regular file,dis a directory,lis a symbolic link - Next 9 characters (
rw-r--r--): permissions. Divided into three groups: owner, group, and others. We’ll dissect these thoroughly in Part 2 - Number (
1/3): hard link count alice: owner userstaff: owner group1024/96: size (bytes)- Date: last modification time
- Last: file/directory name. Names starting with
.are hidden
Absolute and Relative Paths
Paths can be expressed in two ways. An absolute path starts from /. /etc/nginx/nginx.conf is an example. It points to the same file regardless of where you run the command.
A relative path is based on the current location (pwd).
.: current directory..: parent directory~: current user’s home-(cd only): previous directory
cd ~/projects/blog
cat ./package.json # /home/alice/projects/blog/package.json
cat ../notes.md # /home/alice/projects/notes.md
cat ~/.bashrc # /home/alice/.bashrc
cat /etc/hostname # absolute path
When writing scripts, absolute paths are safer. Relative paths can break depending on which directory the script is executed from.
cat, less, head, tail — Peeking Inside Files
Choose the right tool for viewing file contents based on the situation.
# Print the entire file
cat /etc/hostname
# Concatenate and print multiple files (cat's original meaning: conCATenate)
cat file1.txt file2.txt
# Read page by page — q to quit, /keyword to search
less /var/log/syslog
# Just the beginning — 10 lines by default
head /etc/passwd
head -n 3 /etc/passwd
# Just the end — the go-to for checking logs
tail /var/log/syslog
tail -n 100 /var/log/syslog
tail -f /var/log/syslog # Real-time follow
The most frequently used in practice is tail -f. Watching app logs stream by is essentially the same thing kubectl logs -f does in Kubernetes.
You could dump an entire log with cat, but doing that on a multi-million-line log will freeze your terminal for several minutes. Build the habit of using less or tail for large files.
$PATH — How Commands Are Found
When you type ls in the shell, why does /bin/ls get executed? How does the shell know “where the program called ls is”? The secret lies in $PATH.
$PATH is an environment variable that lists directories the shell should search for commands, joined by :.
echo $PATH
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
When the shell receives ls, it checks /usr/local/bin/ls, /usr/bin/ls, /bin/ls in order from the top and executes the first executable it finds. So if a program with the same name exists in two places, the one earlier in $PATH wins.
To check which executable actually runs, use which or type.
which python3
# /usr/bin/python3
type ls
# ls is aliased to `ls --color=auto'
type cd
# cd is a shell builtin
cd is a shell builtin, so there’s no file for it. On the other hand, ls is an actual file at /bin/ls, but depending on the distribution, it often has an alias with --color=auto.
If you want to run your own scripts from anywhere, add their directory to $PATH.
# Add temporarily (current shell only)
export PATH="$HOME/bin:$PATH"
# Add permanently — add a line to ~/.bashrc or ~/.zshrc
echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
The convention is to prepend $HOME/bin at the front. That way your tools take priority even if a system tool with the same name exists.
Environment Variables — The Shell’s Memory
Environment variables are a key-value store shared between the shell and its child processes. They’re case-sensitive, and by convention, written in all uppercase.
# View all environment variables in the current shell
env
# Check individual ones
echo $HOME
echo $USER
echo $LANG
# Set a new variable — only in the current shell (not passed to children)
GREETING="hello"
echo $GREETING
# Must export for child processes to inherit
export GREETING="hello"
bash -c 'echo $GREETING'
# hello
Here’s an important distinction. Variables and environment variables are different. Simply writing X=1 makes it a shell variable — it won’t be visible in child processes. You need export X=1 to make it an environment variable. If a script runs but finds a value empty, suspect this difference first.
The environment variables you’ll encounter most often are roughly these.
| Variable | Meaning |
|---|---|
HOME | Current user’s home directory (/home/alice) |
USER | Logged-in username |
PATH | Executable search path |
PWD | Current working directory |
LANG | Locale (en_US.UTF-8, ko_KR.UTF-8) |
EDITOR | Default editor for tools like git commit |
SHELL | Login shell path |
Prefixing a variable before a program sets the environment variable for that single execution only.
# Change LANG just for this one execution
LANG=C date
# Mon Apr 20 00:30:00 UTC 2026
# Run again and it's back to the original LANG
date
# Mon Apr 20 00:30:00 UTC 2026
The -e KEY=VALUE option when launching Docker containers does essentially the same thing. If you design your app to read configuration from environment variables, you can change its behavior across deployment environments without modifying code. This is exactly what principle #3 of the 12 Factor App talks about.
Shell Configuration Files — Executed on Every Login
You can’t manually set $PATH every time you open a terminal. That’s why the shell has a few configuration files that run automatically at startup.
For bash:
/etc/profile,/etc/bash.bashrc: System-wide settings. Applied to all users~/.bash_profile,~/.bashrc: Personal user settings- A login shell (SSH sessions, etc.) reads
.bash_profile, while an interactive shell (openingbashagain in an already open terminal) reads.bashrc. This difference occasionally causes issues like “it works over SSH but not locally”
For zsh:
~/.zshrc: Nearly all settings go here. Equivalent to bash’s.bashrc
If you’ve modified a configuration file and want to apply it to the current shell, re-read it with the source command.
source ~/.bashrc # or: . ~/.bashrc
source executes the file in the current shell. Running it as bash ~/.bashrc runs it in a child shell, so the current shell’s environment won’t change. Confusing the two can make you think your settings aren’t taking effect.
Peeking into the Kernel via /proc
Let’s open /proc one last time. Understanding why this directory is interesting will help you appreciate just how seriously Linux took its declaration that “everything is a file.”
# CPU information
cat /proc/cpuinfo | head -20
# Memory status
cat /proc/meminfo
# Information about the currently running shell
echo $$ # Current shell's PID
cat /proc/$$/status | head
# Name: bash
# State: S (sleeping)
# Pid: 12345
# PPid: 12344
The files in /proc don’t physically exist on disk. The kernel assembles them as text the moment you request to open them. Nearly all the information from monitoring tools like htop, ps, and free comes from here. We’ll revisit this in Part 3 when discussing processes.
What We Covered in This Part
Here’s a quick recap of the key points.
- The shell is a translator between the user and the kernel. Whether you use bash or zsh, the basic syntax is the same
- The Linux filesystem is a single tree rooted at
/. Knowing just the locations of/etc,/var,/home, and/proccovers most operational needs - Commands are found in directories listed in
$PATH, searched in order. Usewhichandtypeto check the actual path - Environment variables propagate to child processes via
export. Know the precise difference between shell variables and environment variables
In the next part, we’ll look at the rwx permission symbols attached to every file, how chmod, chown, sudo, and umask work, and special permission bits like SUID/SGID.




Loading comments...