Table of Contents
1. What Is Linux? 2. Unix vs Linux 3. Linux Distros & Pop!_OS 4. The Kernel 5. The Shell 6. Terminal & ANSI Escape Codes 7. Linux Directory Structure 8. Users & Permissions 9. Package Management (apt) 10. Bash Scripting Basics 11. .bashrc & Shell Configuration 12. Essential Commands Cheat Sheet 13. Environment Variables & PATH 14. Processes & Services 15. Practice Quiz1. What Is Linux?
Linux is an operating system kernel -- the core piece of software that talks to your hardware (CPU, RAM, disk, GPU) and lets programs run on top of it. It was created in 1991 by Linus Torvalds, a Finnish computer science student, as a free alternative to the expensive Unix operating systems that universities and companies used.
When people say "Linux" they usually mean a complete operating system built around the Linux kernel. Technically, the full stack is:
The kernel alone isn't useful -- you need all the tools around it (shell, file utilities, package manager, display server) to have a working system. That's why some people call it GNU/Linux -- because most of those utilities come from the GNU project (started by Richard Stallman in 1983).
Why Linux Matters for Developers
- Servers run Linux -- over 96% of the world's top web servers run Linux. If you deploy code, you're deploying to Linux.
- It's open source -- you can read the kernel source code, modify it, and understand exactly what your computer is doing. No black boxes.
- The terminal is first-class -- unlike Windows where the terminal is an afterthought, Linux was built around the command line. Most development tools assume a Unix-like environment.
- Containers are Linux -- Docker, Kubernetes, and all container tech runs on the Linux kernel (namespaces + cgroups).
- It's free -- no license costs, no activation keys, no forced updates.
Every command you run on Linux is open source. Curious how ls works? You can read the actual C source code. Wondering what apt does when it installs a package? You can read the Python source. This is incredibly powerful for learning.
2. Unix vs Linux
You'll hear "Unix" and "Linux" used interchangeably, but they're different things with a shared history.
The History (Quick Version)
- 1969 -- Ken Thompson and Dennis Ritchie create Unix at AT&T Bell Labs. It's written in C (which Ritchie also created). Unix introduces revolutionary ideas: "everything is a file", pipes, a hierarchical filesystem, and the shell.
- 1970s-80s -- Unix spreads to universities. Different versions appear: BSD (Berkeley), System V (AT&T), SunOS, HP-UX, AIX. Each is proprietary and expensive.
- 1983 -- Richard Stallman starts the GNU project to create a free Unix-like system. He builds all the user-space tools (compiler, shell, utilities) but the kernel (GNU Hurd) is never finished.
- 1991 -- Linus Torvalds writes the Linux kernel as a hobby project. Combined with GNU tools, it becomes a complete free Unix-like operating system.
- Today -- Linux dominates servers, phones (Android), embedded devices, supercomputers (100% of the top 500), and is increasingly popular on desktops.
Key Differences
| Aspect | Unix | Linux |
|---|---|---|
| Origin | AT&T Bell Labs, 1969 | Linus Torvalds, 1991 |
| License | Proprietary (mostly) | GPL (free and open source) |
| Source code | Closed (mostly) | Fully open |
| Cost | Expensive licenses | Free |
| Examples | macOS, Solaris, HP-UX, AIX | Ubuntu, Fedora, Pop!_OS, Arch, Debian |
| Kernel | Various (XNU for macOS, etc.) | Linux kernel |
| Hardware | Often tied to specific hardware | Runs on almost anything |
| POSIX | Certified POSIX compliant | Mostly POSIX compliant (not certified) |
Unix-like vs Unix
Linux is Unix-like -- it behaves like Unix, follows similar conventions, and programs written for Unix usually work on Linux. But it's not Unix. It doesn't share any code with the original AT&T Unix. It was written from scratch.
macOS is actually certified Unix. Under the hood, macOS runs a Unix kernel called XNU (derived from BSD). That's why the macOS terminal feels similar to Linux -- they share Unix roots.
POSIX -- The Standard
POSIX (Portable Operating System Interface) is a set of standards that defines how a Unix-like operating system should behave. It specifies things like:
- What system calls must exist (open, read, write, fork, exec...)
- How the shell should work
- What utilities must be available (ls, grep, awk, sed...)
- How regular expressions work
Both Unix systems and Linux follow POSIX (mostly). This is why shell scripts and command-line tools work across different Unix-like systems.
Linux is a free, open-source reimplementation of Unix ideas. It's not Unix, but it works like Unix. When a tutorial says "Unix/Linux" or "POSIX", the commands will work on your Pop!_OS system.
3. Linux Distros & Pop!_OS
A Linux distribution (distro) is a complete operating system built around the Linux kernel. Different distros package different software, use different package managers, and target different users. But they all share the same kernel.
The Family Tree
Major Distro Families
| Family | Package Manager | Package Format | Notable Distros |
|---|---|---|---|
| Debian | apt / dpkg | .deb | Debian, Ubuntu, Pop!_OS, Mint |
| Red Hat | dnf / rpm | .rpm | Fedora, RHEL, CentOS, Rocky |
| Arch | pacman | .pkg.tar.zst | Arch, Manjaro, EndeavourOS |
| SUSE | zypper / rpm | .rpm | openSUSE Tumbleweed/Leap |
Your Distro: Pop!_OS
Pop!_OS is made by System76, a company that builds Linux laptops and desktops. Here's what makes it special:
- Based on Ubuntu -- which is based on Debian. So you can use
aptto install packages, and most Ubuntu tutorials work on Pop!_OS. - COSMIC desktop -- System76 built their own desktop environment. It has auto-tiling windows, a clean design, and is written in Rust.
- Pop!_Shop -- a graphical app store (Flatpak-based) for installing software.
- Built for developers -- comes with good defaults, GPU switching for NVIDIA/AMD, and recovery partition.
- systemd-boot -- uses systemd-boot instead of GRUB as the bootloader (simpler and faster).
To check your system info:
Shell
# What distro are you running?
$ cat /etc/os-release
NAME="Pop!_OS"
VERSION="22.04 LTS"
ID=pop
ID_LIKE="ubuntu debian"
# What kernel version?
$ uname -r
6.17.9-76061709-generic
# System architecture
$ uname -m
x86_64
# All system info in one shot
$ hostnamectl
Since Pop!_OS is based on Ubuntu, you can use Ubuntu PPAs, Ubuntu tutorials, and Ubuntu .deb packages. If you're searching for how to do something, search "Ubuntu" + your question -- it will almost always work on Pop!_OS too.
4. The Kernel
The kernel is the core of the operating system. It's the one piece of software that runs in kernel mode (privileged mode) with full access to hardware. Everything else -- your browser, your terminal, your text editor -- runs in user mode and must ask the kernel for permission to do anything hardware-related.
What the Kernel Does
How Programs Talk to the Kernel
Programs can't call kernel functions directly. They use system calls (syscalls) -- a special CPU instruction that switches from user mode to kernel mode. When you call open(), read(), write(), fork(), etc., you're making syscalls. (See the OS page section on syscalls for the full details.)
Shell
# See kernel version
$ uname -r
6.17.9-76061709-generic
# See kernel messages (hardware detection, driver loading, errors)
$ dmesg | tail -20
# See loaded kernel modules (drivers)
$ lsmod
# Kernel configuration
$ cat /boot/config-$(uname -r) | head
# See kernel parameters
$ sysctl -a | head
Monolithic vs Microkernel
Linux is a monolithic kernel -- all the core services (process management, file systems, device drivers, networking) run in kernel space. This is fast because there's no overhead of message passing between components.
The alternative is a microkernel (like GNU Hurd or MINIX) where only the bare minimum runs in kernel mode and everything else runs as user-space servers. More secure and modular, but slower due to inter-process communication overhead.
Linux uses loadable kernel modules as a middle ground -- drivers can be loaded and unloaded at runtime without rebooting:
Shell
# List loaded modules
$ lsmod
# Info about a module
$ modinfo nvidia
# Load a module (requires sudo)
$ sudo modprobe module_name
# Unload a module
$ sudo rmmod module_name
The kernel is just one part of the operating system. It manages hardware and provides services. But without a shell, file utilities, a package manager, and a desktop environment, a kernel alone is useless. That's what distros provide -- everything around the kernel to make a usable system.
5. The Shell
The shell is a program that reads commands you type, interprets them, and asks the kernel to execute them. It's the text-based interface between you and the operating system. When you open a terminal on Pop!_OS, the shell is the program running inside it.
How the Shell Works
The shell is NOT the terminal. The terminal emulator is the GUI window. The shell is the program running inside it that actually processes your commands.
Common Shells
| Shell | Binary | Notes |
|---|---|---|
| bash | /bin/bash | Bourne Again Shell. Default on most Linux distros including Pop!_OS. The one you should learn first. |
| zsh | /bin/zsh | Z Shell. Default on macOS. More features than bash (better autocomplete, themes). Compatible with bash mostly. |
| fish | /usr/bin/fish | Friendly Interactive Shell. Great autosuggestions and syntax highlighting out of the box. NOT bash-compatible. |
| sh | /bin/sh | Bourne Shell (POSIX shell). Minimal. Used for scripts that need maximum portability. |
| dash | /bin/dash | Debian Almquist Shell. What /bin/sh points to on Debian/Ubuntu/Pop!_OS. Very fast, minimal. |
Shell
# What shell am I using?
$ echo $SHELL
/bin/bash
# What shells are installed?
$ cat /etc/shells
# Change your default shell to zsh
$ chsh -s /bin/zsh
# Temporarily use a different shell
$ zsh # starts zsh session
$ exit # back to previous shell
Shell Features You Should Know
Tab Completion
Press Tab to autocomplete commands, file names, and paths. Press Tab twice to see all possibilities. This is the single most important productivity feature. Stop typing full file names.
Command History
Shell
# Search history (most useful shortcut in bash)
Ctrl+R then type part of a previous command
# Show recent commands
$ history | tail -20
# Re-run the last command
$ !!
# Re-run last command starting with "git"
$ !git
# Run previous command with sudo
$ sudo !!
Pipes and Redirection
This is the fundamental Unix philosophy: small programs that do one thing well, connected together with pipes.
Shell
# Pipe: send stdout of one command to stdin of another
$ ls -la | grep ".txt" # list files, filter for .txt
$ cat log.txt | sort | uniq -c # count unique lines
$ ps aux | grep firefox # find firefox processes
# Redirect stdout to a file (overwrites)
$ echo "hello" > output.txt
# Redirect stdout to a file (appends)
$ echo "world" >> output.txt
# Redirect stderr to a file
$ ./my_script 2> errors.txt
# Redirect both stdout and stderr
$ ./my_script > output.txt 2>&1
# Discard output (send to /dev/null -- the void)
$ ./noisy_program > /dev/null 2>&1
# Use a file as stdin
$ sort < unsorted.txt
Keyboard Shortcuts (Bash)
| Shortcut | What It Does |
|---|---|
Ctrl+C | Kill the current running command (sends SIGINT) |
Ctrl+D | Exit the shell / send EOF |
Ctrl+Z | Suspend the current process (put it in background) |
Ctrl+L | Clear the screen |
Ctrl+A | Move cursor to beginning of line |
Ctrl+E | Move cursor to end of line |
Ctrl+W | Delete the word before the cursor |
Ctrl+U | Delete from cursor to beginning of line |
Ctrl+R | Reverse search through command history |
Alt+. | Insert the last argument of the previous command |
1. Aliases (defined with
alias)2. Functions (defined with
function name() {})3. Built-in commands (
cd, echo, export)4. External programs (searched in PATH, left to right)
Check which is used:
type commandname
Learn bash first. It's the default everywhere, all tutorials assume it, and scripts are written in it. Once you're comfortable, try zsh or fish for your interactive shell -- but always know bash because that's what servers run.
6. Terminal & ANSI Escape Codes
The terminal emulator (GNOME Terminal, Alacritty, kitty, etc.) is a GUI application that emulates the old hardware terminals from the 1970s. It displays text, accepts keyboard input, and understands special character sequences called ANSI escape codes.
What's a Terminal?
Terminal emulators pretend to be those old hardware terminals. The protocol they use (ANSI escape sequences) is the same one from the 1970s VT100 terminal, just extended over the decades.
ANSI Escape Codes
An ANSI escape code is a special sequence of characters that starts with ESC[ (escape character followed by [). The terminal interprets these sequences as formatting commands instead of printing them as text.
The escape character is \033 (octal), \x1b (hex), or \e (bash shorthand). It's ASCII code 27.
Text Colors
Shell
# Format: \033[CODEm (the 'm' at the end is required)
# Basic colors (foreground)
echo -e "\033[31mRed text\033[0m"
echo -e "\033[32mGreen text\033[0m"
echo -e "\033[33mYellow text\033[0m"
echo -e "\033[34mBlue text\033[0m"
echo -e "\033[35mMagenta text\033[0m"
echo -e "\033[36mCyan text\033[0m"
# \033[0m resets formatting back to default (always reset!)
# Background colors (add 10 to the foreground code)
echo -e "\033[41mRed background\033[0m"
echo -e "\033[42mGreen background\033[0m"
# Bold, underline, etc.
echo -e "\033[1mBold\033[0m"
echo -e "\033[4mUnderlined\033[0m"
echo -e "\033[1;31mBold Red\033[0m"
# Combine multiple styles with semicolons
echo -e "\033[1;4;33mBold underlined yellow\033[0m"
Color Code Reference
| Code | Foreground | Background |
|---|---|---|
| 30 / 40 | Black | Black |
| 31 / 41 | Red | Red |
| 32 / 42 | Green | Green |
| 33 / 43 | Yellow | Yellow |
| 34 / 44 | Blue | Blue |
| 35 / 45 | Magenta | Magenta |
| 36 / 46 | Cyan | Cyan |
| 37 / 47 | White | White |
| Code | Effect |
|---|---|
| 0 | Reset all formatting |
| 1 | Bold / bright |
| 2 | Dim |
| 4 | Underline |
| 5 | Blink (not supported everywhere) |
| 7 | Reverse (swap foreground/background) |
256 Color and True Color
Shell
# 256 color mode: \033[38;5;COLORm (foreground)
echo -e "\033[38;5;208mOrange text (color 208)\033[0m"
echo -e "\033[48;5;21mBlue background (color 21)\033[0m"
# True color (24-bit RGB): \033[38;2;R;G;Bm
echo -e "\033[38;2;255;100;0mRGB orange\033[0m"
echo -e "\033[48;2;0;50;100mRGB dark blue bg\033[0m"
Cursor Movement
Shell
# Move cursor up/down/forward/back
echo -e "\033[2A" # move up 2 lines
echo -e "\033[3B" # move down 3 lines
echo -e "\033[5C" # move forward 5 columns
echo -e "\033[2D" # move back 2 columns
# Move to specific position (row;column)
echo -e "\033[10;20H" # move to row 10, column 20
# Clear screen
echo -e "\033[2J" # clear entire screen
echo -e "\033[K" # clear from cursor to end of line
# Save and restore cursor position
echo -e "\033[s" # save position
echo -e "\033[u" # restore position
Why ANSI Codes Matter
ANSI codes are how every colorful CLI tool works:
ls --coloruses ANSI codes to color directories blue, executables green, etc.git diffuses red/green ANSI codes for removed/added lines- Your shell prompt (PS1) uses ANSI codes for colors
- Progress bars in
npm install,pip installuse cursor movement codes - Text editors like
vimandnanouse ANSI codes to render the entire UI
Shell
# A colorful bash prompt with username, directory, and git branch
export PS1="\[\033[1;32m\]\u\[\033[0m\]@\[\033[1;34m\]\h\[\033[0m\]:\[\033[1;33m\]\w\[\033[0m\]\$ "
# \u = username, \h = hostname, \w = working directory
# \[...\] tells bash not to count these characters for line wrapping
7. Linux Directory Structure (FHS)
Linux uses a single directory tree starting from / (root). There are no drive letters like C:\ on Windows. Everything -- your files, devices, running processes -- lives somewhere in this tree. The layout follows the Filesystem Hierarchy Standard (FHS).
The Most Important Directories for You
| Directory | What You Use It For |
|---|---|
~ (home) | Your files, projects, configs. cd ~ or just cd goes here. |
/etc | When you need to configure system software (nginx, ssh, network, etc.) |
/var/log | When something breaks -- check the logs here. |
/tmp | Throw temporary files here. Cleared on reboot. |
/usr/bin | Where most programs live. which python3 shows you the exact path. |
/dev | Device files. /dev/null is your best friend for discarding output. |
/proc | Process and system info. cat /proc/cpuinfo shows CPU details. |
Everything Is a File
This is the fundamental Unix philosophy. In Linux, everything is represented as a file:
- Regular files -- text, images, binaries. The obvious ones.
- Directories -- special files that contain lists of other files.
- Device files (
/dev/sda) -- reading/writing these talks directly to hardware. - Pipes (
|) -- connect the output of one program to the input of another. - Sockets -- network connections and inter-process communication.
- Symlinks -- pointers to other files.
- Process info (
/proc/1234/status) -- kernel data exposed as readable files.
Shell
# How much disk space is each top-level directory using?
$ sudo du -sh /* 2>/dev/null | sort -hr | head -10
# What's in /dev? (your devices)
$ ls /dev | head -20
# Read your CPU info (it's just a "file")
$ cat /proc/cpuinfo | grep "model name" | head -1
# How long has your system been running?
$ cat /proc/uptime
# What filesystems are mounted?
$ df -h
8. Users & Permissions
Linux is a multi-user system. Every file is owned by a user and a group, and has permission bits that control who can read, write, or execute it.
Users and Groups
Shell
# Who am I?
$ whoami
sean
# What groups am I in?
$ groups
sean adm cdrom sudo dip plugdev lpadmin
# User info (from /etc/passwd)
$ id
uid=1000(sean) gid=1000(sean) groups=1000(sean),27(sudo),...
# The root user (UID 0) can do ANYTHING
# Use sudo to run commands as root
$ sudo apt update
Permission Bits
For files: r = read the contents, w = modify it, x = run it as a program.
For directories: r = list contents (ls), w = create/delete files inside, x = enter the directory (cd).
Shell
# Change permissions (numeric)
$ chmod 755 script.sh # rwxr-xr-x (owner: all, group/others: read+exec)
$ chmod 644 notes.txt # rw-r--r-- (owner: read+write, group/others: read)
$ chmod 700 private/ # rwx------ (owner only)
# Change permissions (symbolic)
$ chmod +x script.sh # add execute for everyone
$ chmod u+x script.sh # add execute for owner only
$ chmod go-w file.txt # remove write for group and others
# Change ownership
$ sudo chown sean:sean file.txt
$ sudo chown -R sean:sean directory/ # recursive
sudo -- Doing Things as Root
sudo (superuser do) runs a single command as root. Your user must be in the sudo group. On Pop!_OS, your first user is automatically in the sudo group.
Shell
# Install software (needs root)
$ sudo apt install git
# Edit a system config file
$ sudo nano /etc/hosts
# Become root for multiple commands (use sparingly)
$ sudo -i # opens a root shell
# exit # go back to your user
# Run the last command with sudo
$ apt update # oops, permission denied
$ sudo !! # runs "sudo apt update"
Only use sudo when you actually need elevated privileges (installing software, editing system files, managing services). Running everyday commands as root is dangerous -- a typo like sudo rm -rf / would destroy your entire system. The permission system exists to protect you from yourself.
9. Package Management (apt)
On Linux, you don't download .exe files from websites. Instead, you use a package manager that downloads, installs, updates, and removes software from trusted repositories (servers that host packages).
Pop!_OS uses apt (Advanced Package Tool) because it's Debian/Ubuntu-based. Packages are .deb files.
How apt Works
Essential apt Commands
Shell
# === ALWAYS UPDATE FIRST ===
$ sudo apt update # refresh package lists (what's available)
$ sudo apt upgrade # upgrade all installed packages
$ sudo apt full-upgrade # upgrade + handle dependency changes
# === INSTALL ===
$ sudo apt install git # install a package
$ sudo apt install git curl wget # install multiple at once
$ sudo apt install -y git # auto-answer yes to prompts
# === REMOVE ===
$ sudo apt remove firefox # remove package (keeps config files)
$ sudo apt purge firefox # remove package + config files
$ sudo apt autoremove # remove unused dependencies
# === SEARCH ===
$ apt search "text editor" # search for packages
$ apt show git # show details about a package
$ apt list --installed # list all installed packages
$ apt list --installed | grep python # find installed python packages
# === INFO ===
$ dpkg -L git # list all files installed by a package
$ dpkg -S /usr/bin/git # which package owns this file?
$ apt policy git # show installed and available versions
Repositories (PPAs)
Sometimes the version in the default repositories is old. You can add extra PPAs (Personal Package Archives) for newer or third-party software:
Shell
# Add a PPA (example: newer version of something)
$ sudo add-apt-repository ppa:some-ppa/name
$ sudo apt update
$ sudo apt install package-name
# Remove a PPA
$ sudo add-apt-repository --remove ppa:some-ppa/name
Other Ways to Install Software on Pop!_OS
| Method | Command | When to Use |
|---|---|---|
| apt | sudo apt install | Default. Use this first. |
| Flatpak | flatpak install | Sandboxed apps. Pop!_Shop uses this. Good for GUI apps. |
| Snap | snap install | Canonical's format. Some apps only available here. |
| .deb file | sudo dpkg -i file.deb | Downloaded .deb from a website (e.g., VS Code, Discord). |
| AppImage | chmod +x app.AppImage && ./app.AppImage | Portable apps. Just download and run. |
| From source | ./configure && make && sudo make install | Last resort. Compiling from source code. |
Run sudo apt update && sudo apt upgrade regularly (at least weekly). This gets you security patches and bug fixes. Pop!_OS also shows updates in the system tray. Don't ignore them -- they include kernel and security updates.
10. Bash Scripting Basics
A Bash script is a text file containing a series of commands that bash executes one by one. Instead of typing commands manually, you write them in a file and run the file. This lets you automate repetitive tasks.
set -euo pipefail•
-e: Exit immediately if any command fails•
-u: Treat unset variables as errors•
-o pipefail: Pipe fails if ANY command in the pipe fails (not just the last)
Your First Script
Bash
#!/bin/bash
# The first line (shebang) tells the OS to use bash to run this file
# Print a greeting
echo "Hello, $(whoami)!"
echo "Today is $(date +%A)"
echo "You are in: $(pwd)"
Shell
# Save as greeting.sh, make executable, then run
$ chmod +x greeting.sh
$ ./greeting.sh
Hello, sean!
Today is Sunday
You are in: /home/sean
Variables
Bash
#!/bin/bash
# Assign variables (NO spaces around =)
name="Sean"
age=25
directory="/home/sean"
# Use variables with $ prefix
echo "Name: $name"
echo "Age: $age"
# Curly braces for clarity (needed when variable is next to other text)
echo "${name}'s files are in ${directory}"
# Command substitution: capture command output in a variable
current_date=$(date +%Y-%m-%d)
file_count=$(ls | wc -l)
echo "Date: $current_date, Files in current dir: $file_count"
# Read user input
read -p "Enter your name: " user_name
echo "Hello, $user_name!"
name="Sean" works. name = "Sean" does NOT -- bash thinks name is a command. This catches everyone at first.
Conditionals (if/else)
Bash
#!/bin/bash
# Check if a file exists
if [ -f "$HOME/.bashrc" ]; then
echo ".bashrc exists"
else
echo ".bashrc not found"
fi
# Check if a directory exists
if [ -d "/tmp" ]; then
echo "/tmp is a directory"
fi
# String comparison
name="sean"
if [ "$name" = "sean" ]; then
echo "Hello Sean!"
fi
# Number comparison
count=5
if [ "$count" -gt 3 ]; then
echo "Count is greater than 3"
elif [ "$count" -eq 3 ]; then
echo "Count is exactly 3"
else
echo "Count is less than 3"
fi
# Check if a command exists
if command -v git &>/dev/null; then
echo "git is installed: $(git --version)"
else
echo "git is not installed"
fi
Test Operators Reference
| Operator | Meaning |
|---|---|
-f file | File exists and is a regular file |
-d dir | Directory exists |
-e path | Path exists (file or directory) |
-r file | File is readable |
-w file | File is writable |
-x file | File is executable |
-z "$var" | String is empty |
-n "$var" | String is not empty |
"$a" = "$b" | Strings are equal |
"$a" != "$b" | Strings are not equal |
$a -eq $b | Numbers are equal |
$a -ne $b | Numbers are not equal |
$a -gt $b | a greater than b |
$a -lt $b | a less than b |
$a -ge $b | a greater than or equal to b |
$a -le $b | a less than or equal to b |
Loops
Bash
#!/bin/bash
# For loop (iterate over a list)
for fruit in apple banana cherry; do
echo "I like $fruit"
done
# For loop (iterate over files)
for file in *.txt; do
echo "Processing: $file"
wc -l "$file"
done
# For loop (C-style, count from 1 to 5)
for (( i=1; i<=5; i++ )); do
echo "Count: $i"
done
# While loop
count=0
while [ "$count" -lt 5 ]; do
echo "Count is $count"
count=$(( count + 1 ))
done
# Read a file line by line
while IFS= read -r line; do
echo "Line: $line"
done < input.txt
Functions
Bash
#!/bin/bash
# Define a function
greet() {
local name="$1" # $1 is the first argument
echo "Hello, $name!"
}
# Call it
greet "Sean"
greet "World"
# Function with return value (actually exit code, 0-255)
is_even() {
if (( $1 % 2 == 0 )); then
return 0 # success (true)
else
return 1 # failure (false)
fi
}
if is_even 4; then
echo "4 is even"
fi
# Function that outputs a value (capture with $())
get_extension() {
echo "${1##*.}" # parameter expansion: get text after last .
}
ext=$(get_extension "photo.jpg")
echo "Extension: $ext" # jpg
Script Arguments
Bash
#!/bin/bash
# Save as args.sh
echo "Script name: $0"
echo "First arg: $1"
echo "Second arg: $2"
echo "All args: $@"
echo "Num of args: $#"
echo "Exit code of last command: $?"
# Usage: ./args.sh hello world
# Script name: ./args.sh
# First arg: hello
# Second arg: world
# All args: hello world
# Num of args: 2
Bash
#!/bin/bash
# backup.sh -- backup a directory to a timestamped tar.gz
if [ -z "$1" ]; then
echo "Usage: $0 <directory>"
exit 1
fi
if [ ! -d "$1" ]; then
echo "Error: '$1' is not a directory"
exit 1
fi
timestamp=$(date +%Y%m%d_%H%M%S)
dirname=$(basename "$1")
backup_file="${dirname}_backup_${timestamp}.tar.gz"
tar -czf "$backup_file" "$1"
echo "Backup created: $backup_file ($(du -h "$backup_file" | cut -f1))"
11. .bashrc & Shell Configuration
When you open a terminal, bash reads configuration files to set up your environment. The most important one is ~/.bashrc -- this is where you customise your shell experience.
Which Files Are Read and When
| File | When It's Read | What It's For |
|---|---|---|
~/.bashrc | Every new interactive non-login shell (opening a terminal) | Aliases, functions, prompt, shell options. This is the main one. |
~/.bash_profile | Login shells only (SSH, tty login, first terminal on some systems) | Environment variables (PATH, etc.). Usually sources .bashrc. |
~/.profile | Login shells (if no .bash_profile exists) | Same as .bash_profile. Used by Pop!_OS by default. |
/etc/bash.bashrc | Every interactive bash shell | System-wide defaults. Don't edit this -- use your ~/.bashrc. |
/etc/profile | All login shells | System-wide login setup. |
What to Put in .bashrc
Bash
# === ALIASES ===
# Shortcuts for commands you type frequently
alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'
# Safety aliases (ask before overwriting)
alias cp='cp -i'
alias mv='mv -i'
alias rm='rm -i'
# Git shortcuts
alias gs='git status'
alias ga='git add'
alias gc='git commit'
alias gp='git push'
alias gl='git log --oneline --graph -10'
alias gd='git diff'
# Navigation shortcuts
alias ..='cd ..'
alias ...='cd ../..'
alias dev='cd ~/Documents/dev'
# System shortcuts
alias update='sudo apt update && sudo apt upgrade'
alias ports='ss -tulnp'
alias myip='curl -s ifconfig.me'
# === FUNCTIONS ===
# More powerful than aliases
# Create a directory and cd into it
mkcd() {
mkdir -p "$1" && cd "$1"
}
# Extract any archive
extract() {
case "$1" in
*.tar.gz|*.tgz) tar -xzf "$1" ;;
*.tar.bz2) tar -xjf "$1" ;;
*.tar.xz) tar -xJf "$1" ;;
*.zip) unzip "$1" ;;
*.gz) gunzip "$1" ;;
*.7z) 7z x "$1" ;;
*) echo "Unknown format: $1" ;;
esac
}
# === SHELL OPTIONS ===
shopt -s histappend # append to history, don't overwrite
shopt -s cdspell # autocorrect small typos in cd
shopt -s autocd # type a directory name to cd into it
# === HISTORY ===
HISTSIZE=10000 # remember 10000 commands
HISTFILESIZE=20000 # store 20000 in history file
HISTCONTROL=ignoredups:erasedups # no duplicate entries
# === CUSTOM PROMPT ===
# Green user, blue directory, $ sign
PS1='\[\033[1;32m\]\u\[\033[0m\]:\[\033[1;34m\]\w\[\033[0m\]\$ '
Applying Changes
Shell
# After editing .bashrc, apply changes without restarting terminal
$ source ~/.bashrc
# or
$ . ~/.bashrc
# Open .bashrc to edit
$ nano ~/.bashrc
# or
$ code ~/.bashrc # if you have VS Code
Don't copy a huge .bashrc from the internet. Start with a few aliases you'll actually use. Add more as you find yourself typing the same things repeatedly. Your .bashrc should be yours -- customised to your workflow.
12. Essential Commands Cheat Sheet
These are the commands you'll use daily. Master them and you'll never feel lost in a terminal again.
Navigation & Files
Shell
# Where am I?
$ pwd # print working directory
# Move around
$ cd /path/to/dir # go to directory
$ cd ~ # go home (or just: cd)
$ cd .. # go up one level
$ cd - # go to previous directory
# List files
$ ls # basic listing
$ ls -la # detailed + hidden files
$ ls -lh # human-readable file sizes
$ ls -lt # sorted by modification time
# Create
$ touch file.txt # create empty file (or update timestamp)
$ mkdir mydir # create directory
$ mkdir -p a/b/c # create nested directories
# Copy
$ cp source.txt dest.txt # copy file
$ cp -r source_dir/ dest/ # copy directory recursively
# Move / Rename
$ mv old.txt new.txt # rename
$ mv file.txt ~/Documents/ # move
# Delete
$ rm file.txt # delete file
$ rm -r directory/ # delete directory and contents
$ rm -i file.txt # ask for confirmation first
Reading & Searching Files
Shell
# Read entire file
$ cat file.txt # print to screen
$ less file.txt # scrollable viewer (q to quit)
# Read parts of a file
$ head -20 file.txt # first 20 lines
$ tail -20 file.txt # last 20 lines
$ tail -f logfile.txt # follow (live updates -- great for logs)
# Search inside files
$ grep "pattern" file.txt # find lines matching pattern
$ grep -r "TODO" . # search recursively in all files
$ grep -i "error" log.txt # case-insensitive search
$ grep -n "function" *.js # show line numbers
$ grep -c "error" log.txt # count matches
# Find files by name
$ find . -name "*.py" # find all .py files
$ find / -name "nginx.conf" # find a config file
$ find . -type d -name node_modules # find directories
$ find . -size +100M # files larger than 100MB
# Count lines/words/characters
$ wc -l file.txt # count lines
$ wc -w file.txt # count words
Disk & System Info
Shell
# Disk usage
$ df -h # filesystem usage
$ du -sh ~/Documents # directory size
$ du -sh */ | sort -hr # biggest directories first
# System info
$ uname -a # kernel and system info
$ lsb_release -a # distro info
$ free -h # memory usage
$ uptime # how long the system has been running
$ nproc # number of CPU cores
# Who and what
$ whoami # current user
$ hostname # machine name
$ which python3 # full path of a command
$ type ls # is it an alias, function, or binary?
Text Processing
Shell
# Sort lines
$ sort file.txt # alphabetical sort
$ sort -n file.txt # numeric sort
$ sort -r file.txt # reverse sort
# Remove duplicates (input must be sorted)
$ sort file.txt | uniq # unique lines
$ sort file.txt | uniq -c # count occurrences
# Cut columns from data
$ cut -d',' -f1,3 data.csv # extract columns 1 and 3 from CSV
$ cut -d':' -f1 /etc/passwd # list all usernames
# Stream editor (search and replace)
$ sed 's/old/new/g' file.txt # replace all occurrences
$ sed -i 's/old/new/g' file.txt # edit file in-place
# Awk (powerful text processing)
$ awk '{print $1}' file.txt # print first column
$ awk -F',' '{print $2}' data.csv # print second column of CSV
Networking
Shell
# Download files
$ wget https://example.com/file.tar.gz
$ curl -O https://example.com/file.tar.gz
# Make HTTP requests
$ curl https://api.example.com/data
$ curl -X POST -d '{"key":"value"}' https://api.example.com
# Network info
$ ip addr # show IP addresses
$ ping google.com # test connectivity
$ ss -tulnp # show open ports
$ dig example.com # DNS lookup
Every command has a manual page. Type man ls to read the full documentation for ls. Press q to quit. Use /pattern to search within the man page. If man pages are too dense, try tldr ls (install with sudo apt install tldr) for simplified examples.
13. Environment Variables & PATH
Environment variables are key-value pairs that every process inherits from its parent. They configure how programs behave without editing config files.
Shell
# See all environment variables
$ env
# See a specific one
$ echo $HOME # /home/sean
$ echo $USER # sean
$ echo $SHELL # /bin/bash
$ echo $TERM # xterm-256color
$ echo $EDITOR # (your default text editor)
$ echo $LANG # en_US.UTF-8
# Set a variable (current session only)
$ export MY_VAR="hello"
$ echo $MY_VAR # hello
# Set permanently (add to ~/.bashrc)
export EDITOR="nano"
export MY_API_KEY="abc123"
PATH -- The Most Important Variable
PATH is a colon-separated list of directories where the shell looks for executables. When you type git, the shell searches each directory in PATH left-to-right until it finds a git binary.
Shell
# See your PATH
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin
# See it one directory per line
$ echo $PATH | tr ':' '\n'
/usr/local/bin
/usr/bin
/bin
/usr/local/sbin
/usr/sbin
# Where is a command located?
$ which python3 # /usr/bin/python3
$ which git # /usr/bin/git
# Add a directory to PATH (in ~/.bashrc)
export PATH="$HOME/.local/bin:$PATH"
# This prepends your local bin directory, so programs
# installed there are found before system ones
Common PATH Additions
Bash
# Add these to ~/.bashrc if you need them
# Your personal scripts
export PATH="$HOME/.local/bin:$PATH"
# Node.js (nvm)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Go
export PATH="$PATH:/usr/local/go/bin"
export PATH="$PATH:$HOME/go/bin"
# Rust (cargo)
export PATH="$HOME/.cargo/bin:$PATH"
# Python (pip --user installs)
export PATH="$HOME/.local/bin:$PATH"
If you install something and get "command not found", it means the binary isn't in your PATH. Find where it was installed (find / -name "binary_name" 2>/dev/null) and add that directory to your PATH in ~/.bashrc.
14. Processes & Services
Every running program is a process. The OS page covers processes in depth -- here we focus on practical commands for managing them on your Pop!_OS system.
Viewing Processes
Shell
# See all running processes
$ ps aux # classic Unix format
$ ps aux | grep firefox # find a specific process
# Interactive process viewer (much better than ps)
$ htop # install: sudo apt install htop
# Process tree (shows parent-child relationships)
$ pstree -p
# What's using the most CPU?
$ ps aux --sort=-%cpu | head -10
# What's using the most memory?
$ ps aux --sort=-%mem | head -10
Managing Processes
Shell
# Run a command in the background
$ ./long_task & # & puts it in the background
$ jobs # list background jobs
$ fg %1 # bring job 1 to foreground
# Ctrl+Z to suspend a running process, then:
$ bg # resume it in the background
# Kill processes
$ kill 1234 # send SIGTERM (polite: "please stop")
$ kill -9 1234 # send SIGKILL (force kill -- can't be ignored)
$ killall firefox # kill all processes named firefox
$ pkill -f "python server" # kill processes matching a pattern
systemd -- Managing Services
Pop!_OS uses systemd to manage services (background programs that start automatically). Web servers, databases, SSH, bluetooth -- all managed by systemd.
Shell
# Check status of a service
$ systemctl status nginx
$ systemctl status ssh
$ systemctl status bluetooth
# Start / stop / restart a service
$ sudo systemctl start nginx
$ sudo systemctl stop nginx
$ sudo systemctl restart nginx
# Enable (start on boot) / disable
$ sudo systemctl enable nginx # start automatically on boot
$ sudo systemctl disable nginx # don't start on boot
# List all running services
$ systemctl list-units --type=service --state=running
# See service logs (journalctl)
$ journalctl -u nginx # logs for nginx
$ journalctl -u nginx --since today
$ journalctl -f # follow all logs in real-time
You might see older tutorials use sudo service nginx restart. That still works (it's a compatibility wrapper), but systemctl is the modern way and gives you more control.