← All stacks
SH

Bash & Linux

12 sections · 107 entries

Navigation & Files

Change Directory

syntax
cd [directory]
example
cd /var/log
cd ~
cd ..
cd -

Note cd with no argument returns to your home directory. cd - switches to the previous directory you were in, which is great for toggling between two locations.

List Directory Contents

syntax
ls [options] [directory]
example
ls -la /etc
ls -lhS ~/Downloads
ls -lt --color=auto
output
drwxr-xr-x  5 deploy staff  160 Mar 12 09:14 config
-rw-r--r--  1 deploy staff 2.4K Mar 11 18:30 app.js

Note -l for long format, -a includes hidden files (dotfiles), -h for human-readable sizes, -S sorts by size, -t sorts by modification time (newest first), -R lists recursively.

Print Working Directory

syntax
pwd [-P]
example
pwd
output
/home/deploy/projects/web-app

Note Use -P to resolve symlinks and show the physical path rather than the logical one.

Create Directories

syntax
mkdir [options] directory...
example
mkdir -p src/components/auth
mkdir -m 750 secrets

Note -p creates the full path including any missing parent directories and will not error if the directory already exists. -m sets permissions at creation time.

Copy Files & Directories

syntax
cp [options] source destination
example
cp config.yml config.yml.bak
cp -r templates/ /opt/app/templates/
cp -i report.csv ~/Desktop/

Note -r is required for directories (recursive copy). -i prompts before overwriting. Without -i, cp silently overwrites the destination. Use -a to preserve permissions, timestamps, and symlinks.

Move or Rename Files

syntax
mv [options] source destination
example
mv old_name.js new_name.js
mv *.log /var/archive/
mv -i data.db /backups/

Note mv both renames and relocates. It silently overwrites the target by default. Use -i to prompt before overwriting or -n to never overwrite.

Remove Files & Directories

syntax
rm [options] file...
example
rm debug.log
rm -r old_build/
rm -ri temp_data/

Note WARNING: rm is permanent. There is no trash can. -r removes directories recursively, -f forces removal without prompts. Never run rm -rf / or rm -rf * without double-checking your current directory with pwd first. Use -i for interactive confirmation on important files.

Create Empty File / Update Timestamp

syntax
touch [options] file...
example
touch index.html
touch -t 202601151030 report.pdf

Note If the file exists, touch updates its modification timestamp without changing content. With -t, you can set a specific timestamp in [[CC]YY]MMDDhhmm[.ss] format.

Find Files by Criteria

syntax
find [path] [expression]
example
find /var/log -name '*.log' -mtime -7
find . -type f -size +100M
find src/ -name '*.ts' -exec wc -l {} +
output
/var/log/syslog
/var/log/auth.log

Note -name is case-sensitive; use -iname for case-insensitive. -mtime -7 means modified in the last 7 days. -exec runs a command on each result; use + instead of \; to batch them for better performance.

Display Directory Tree

syntax
tree [options] [directory]
example
tree -L 2 --dirsfirst
tree -I 'node_modules|.git' -a
output
.
├── src/
│   ├── index.ts
│   └── utils/
├── package.json
└── tsconfig.json

Note -L limits depth, -I excludes patterns (pipe-separated), -a shows hidden files, --dirsfirst groups directories at the top. Not installed by default on all systems; install via your package manager.

Create Links

syntax
ln [options] target link_name
example
ln -s /opt/app/current/config.yml ~/config.yml
ln -sf /usr/bin/python3 /usr/local/bin/python

Note Without -s, a hard link is created (shares the same inode; cannot span filesystems). Symbolic links (-s) are like shortcuts and can point anywhere, including directories. -f replaces an existing link.

File Content

Display File Contents

syntax
cat [options] file...
example
cat server.log
cat -n deploy.sh
cat header.html body.html footer.html > page.html

Note -n adds line numbers, -A shows invisible characters (tabs, line endings). cat is best for short files; use less for anything longer than a screenful. Concatenating multiple files into one is where cat gets its name.

View Beginning of File

syntax
head [options] file
example
head -n 20 access.log
head -c 512 firmware.bin

Note -n specifies number of lines (default 10). -c specifies number of bytes. Useful for quickly inspecting CSV headers or log files.

View End of File / Follow Logs

syntax
tail [options] file
example
tail -n 50 error.log
tail -f /var/log/nginx/access.log
tail -f app.log | grep --line-buffered 'ERROR'

Note -f follows the file in real-time as new lines are appended, which is indispensable for monitoring logs. Use Ctrl+C to stop following. -F will keep retrying if the file is rotated or recreated.

Page Through Files

syntax
less [options] file
example
less +G server.log
less -N config.yaml

Note Navigate: Space (page down), b (page up), /pattern (search forward), ?pattern (search backward), n/N (next/previous match), g/G (start/end), q (quit). +G opens at the end of the file. -N shows line numbers.

Word, Line & Byte Count

syntax
wc [options] file...
example
wc -l src/*.py
wc -w essay.txt
find . -name '*.go' | xargs wc -l | tail -1
output
  142  387  4821 app.py
   38   95  1102 utils.py
  180  482  5923 total

Note -l counts lines, -w counts words, -c counts bytes, -m counts characters. With no flags, all three are shown. Piping with find is a quick way to count lines across an entire project.

Sort Lines

syntax
sort [options] file
example
sort -u names.txt
sort -t',' -k3 -n sales.csv
du -sh */ | sort -rh

Note -u removes duplicates, -n sorts numerically, -r reverses order, -h sorts human-readable sizes (1K, 2M, 3G), -t sets field delimiter, -k specifies which field to sort by.

Filter Duplicate Lines

syntax
uniq [options] [input [output]]
example
sort access.log | uniq -c | sort -rn | head -20
output
    847 GET /api/users
    623 GET /api/health
    412 POST /api/login

Note uniq only removes adjacent duplicates, so you almost always need to sort first. -c prefixes each line with its count, -d shows only duplicates, -i ignores case.

Compare Files

syntax
diff [options] file1 file2
example
diff old_config.ini new_config.ini
diff -u main.py main_v2.py > changes.patch
diff -rq dir_a/ dir_b/
output
--- old_config.ini
+++ new_config.ini
@@ -3,4 +3,4 @@
-timeout=30
+timeout=60

Note -u produces unified format (most readable). -r compares directories recursively. -q shows only whether files differ, not how. --color adds color output on supported systems.

Identify File Type

syntax
file [options] file...
example
file mystery_attachment
file -i database.dump
output
mystery_attachment: PDF document, version 1.7
database.dump: application/octet-stream; charset=binary

Note file examines the file's content (magic bytes), not the extension. -i outputs MIME type. Useful when you receive a file with no extension or a misleading one.

Detailed File Metadata

syntax
stat [options] file
example
stat package.json
output
  File: package.json
  Size: 1843       Blocks: 8       IO Block: 4096  regular file
Access: (0644/-rw-r--r--)  Uid: (1000/deploy)  Gid: (1000/staff)
Modify: 2026-03-28 14:22:10.000000000 +0000

Note Shows size, permissions (numeric and symbolic), ownership, inode, timestamps (access, modify, change), and more. On macOS, the output format differs slightly from GNU stat.

Permissions & Ownership

Change Permissions (Numeric)

syntax
chmod [options] mode file...
example
chmod 755 deploy.sh
chmod 644 index.html
chmod -R 750 /opt/app/

Note Digits represent owner/group/others. Each is a sum: read=4, write=2, execute=1. So 755 = rwxr-xr-x, 644 = rw-r--r--. Common modes: 755 for scripts/directories, 644 for regular files, 600 for private keys.

Change Permissions (Symbolic)

syntax
chmod [who][+/-/=][permissions] file...
example
chmod u+x build.sh
chmod go-w config.yml
chmod a+r public/favicon.ico

Note who: u=user/owner, g=group, o=others, a=all. Operators: + adds, - removes, = sets exactly. Symbolic mode is safer for targeted changes because you modify only what you specify, unlike numeric which replaces all bits at once.

Change File Owner

syntax
chown [options] user[:group] file...
example
sudo chown www-data:www-data /var/www/html -R
sudo chown deploy app/
sudo chown :staff shared/

Note Requires root/sudo for files you do not own. user:group changes both at once. :group (with colon, no user) changes only the group. -R applies recursively. Be careful with -R on directories containing symlinks; use --no-dereference to avoid following them.

Change Group Ownership

syntax
chgrp [options] group file...
example
sudo chgrp developers /opt/shared-repo -R

Note Equivalent to chown :group. -R applies recursively. You can only change to a group you belong to unless you are root.

Set Default Permission Mask

syntax
umask [mode]
example
umask
umask 027
output
0022

Note umask is subtracted from maximum permissions (777 for dirs, 666 for files). Default umask 022 means new files get 644 and new directories get 755. A umask of 027 gives files 640 and dirs 750, hiding content from 'others'.

Execute as Superuser

syntax
sudo [options] command
example
sudo apt update
sudo -u postgres psql
sudo !!

Note sudo !! reruns the last command with sudo (great when you forget to prefix it). -u runs as a specific user. Sudo access is configured in /etc/sudoers (edit only with visudo, never directly). Your password is cached for a short time after the first entry.

Switch User

syntax
su [options] [user]
example
su - deploy
su -c 'systemctl restart nginx' root

Note su - (with the dash) starts a login shell and loads the target user's environment. Without the dash, you inherit the current environment. -c runs a single command as that user and returns.

Special Bits: Sticky, SUID & SGID

syntax
chmod [1|2|4]nnn file
chmod +t directory
chmod g+s directory
example
chmod 1777 /tmp
chmod 2775 /opt/team-share/
chmod u+s /usr/bin/passwd

Note Sticky bit (1 or +t): only the file owner can delete their files in a shared directory (used on /tmp). SGID (2 or g+s) on a directory: new files inherit the directory's group. SUID (4 or u+s) on an executable: it runs as the file owner, not the caller. SUID/SGID are security-sensitive; audit carefully.

Process Management

List Running Processes

syntax
ps [options]
example
ps aux
ps aux | grep '[n]ginx'
ps -ef --forest
output
USER   PID %CPU %MEM    VSZ   RSS TTY  STAT START   TIME COMMAND
root     1  0.0  0.1 168940 11788 ?   Ss   Mar28   0:09 /sbin/init

Note aux shows all users' processes in BSD format. -ef is the System V equivalent. Wrapping one letter in brackets (e.g., '[n]ginx') in the grep pattern avoids matching the grep process itself in the results.

Interactive Process Viewer

syntax
top
htop
example
top -o %MEM
htop -u deploy

Note top is always available. In top: press M to sort by memory, P by CPU, k to kill a process, q to quit. htop is a friendlier alternative with mouse support, color, and tree view but may need installing. -u filters by user.

Send Signal to a Process

syntax
kill [signal] PID...
example
kill 28401
kill -9 28401
kill -HUP $(cat /var/run/nginx.pid)

Note Default signal is TERM (15), which asks the process to shut down gracefully. Use -9 (KILL) only as a last resort; it cannot be caught and may leave temp files or corrupt data. -HUP (1) tells many daemons to reload configuration.

Kill Processes by Name

syntax
killall [options] name
example
killall -TERM node
killall -u deploy python3

Note Matches by process name, not PID. -u restricts to a specific user's processes. WARNING: On Solaris, killall without arguments kills ALL processes, which is catastrophic. On Linux it requires a name argument and is safe.

Background, Foreground & Job List

syntax
command &
bg [%job]
fg [%job]
jobs
example
python3 train_model.py &
jobs
fg %1
output
[1]+  Running    python3 train_model.py &

Note Ctrl+Z suspends a foreground job. bg resumes it in the background. fg brings a background job to the foreground. jobs lists all jobs for the current shell session. %1 refers to job number 1.

Run Process After Logout

syntax
nohup command [args] &
example
nohup python3 etl_pipeline.py > etl.log 2>&1 &

Note nohup prevents the process from receiving SIGHUP when your shell exits. Output goes to nohup.out by default unless redirected. For more robust session persistence, consider tmux or screen.

Find or Kill Processes by Pattern

syntax
pgrep [options] pattern
pkill [options] pattern
example
pgrep -la nginx
pkill -f 'python3 worker.py'
pgrep -u deploy -c
output
14823 nginx: master process
14824 nginx: worker process

Note -l shows the process name alongside the PID. -f matches against the full command line, not just the process name. -u filters by user. pkill sends SIGTERM by default; add -9 for SIGKILL.

List Open Files & Ports

syntax
lsof [options]
example
lsof -i :3000
lsof -u deploy
lsof +D /var/log/
output
COMMAND   PID   USER   FD   TYPE  DEVICE SIZE/OFF NODE NAME
node    19283 deploy   22u  IPv4  284719      0t0  TCP *:3000 (LISTEN)

Note -i :port shows which process is using a port (essential for resolving 'address already in use' errors). -u filters by user. +D lists all open files within a directory. Requires root for other users' processes.

Wait for Background Jobs

syntax
wait [pid|%job]
example
process_a &
process_b &
wait
echo 'Both finished'

Note wait with no arguments paits for all background jobs. With a PID or job spec, it waits for just that one. Useful in scripts to parallelize tasks and then synchronize before continuing.

Text Processing

Search Text with Patterns

syntax
grep [options] pattern [file...]
example
grep -rn 'TODO' src/
grep -i 'error' /var/log/syslog
grep -c 'SELECT' queries.sql
grep -v '^#' nginx.conf
output
src/api/handler.go:42:  // TODO: add rate limiting
src/lib/cache.ts:18:  // TODO: implement TTL

Note -r searches recursively through directories. -n shows line numbers. -i is case-insensitive. -v inverts the match (shows non-matching lines). -c counts matches. -l lists only filenames with matches. Use -E for extended regex or egrep.

Stream Editor for Find & Replace

syntax
sed [options] 's/pattern/replacement/flags' file
example
sed 's/localhost/0.0.0.0/g' config.ini
sed -i.bak 's/v1\.2/v1.3/g' version.txt
sed -n '10,20p' access.log
sed '/^$/d' notes.txt

Note -i edits in place. -i.bak creates a backup before editing (strongly recommended). g flag replaces all occurrences on a line, not just the first. -n suppresses default output; use with p to print specific lines. /d deletes matched lines.

Column-Based Text Processing

syntax
awk 'pattern { action }' file
example
awk '{print $1, $4}' access.log
awk -F',' '$3 > 1000 {print $1, $3}' sales.csv
awk '{sum += $5} END {print "Total:", sum}' report.tsv
output
192.168.1.40 [28/Mar/2026:10:15:23
Alice 2340
Total: 87450

Note By default, awk splits on whitespace. -F sets a custom delimiter. $0 is the whole line, $1 is the first field, NR is the line number, NF is the number of fields. BEGIN runs before processing; END runs after.

Extract Fields or Characters

syntax
cut [options] file
example
cut -d',' -f1,3 employees.csv
cut -c1-10 logfile.txt
output
name,department
Alice,Engineering
Bob,Marketing

Note -d sets the delimiter (default is tab). -f selects fields by number. -c selects character positions. For more complex extraction, awk is usually more flexible.

Translate or Delete Characters

syntax
tr [options] set1 [set2]
example
echo 'Hello World' | tr 'A-Z' 'a-z'
cat data.csv | tr ',' '\t'
tr -d '\r' < windows_file.txt > unix_file.txt
output
hello world

Note tr reads only from stdin (it cannot take a filename argument). -d deletes characters in set1. -s squeezes repeated characters. Converting \r is handy for fixing Windows line endings.

Build Commands from Stdin

syntax
xargs [options] command
example
find . -name '*.tmp' -print0 | xargs -0 rm
cat urls.txt | xargs -P 4 -I {} curl -sO {}
grep -rl 'oldFunc' src/ | xargs sed -i 's/oldFunc/newFunc/g'

Note -0 with find -print0 handles filenames with spaces and special characters safely. -I {} replaces {} with each input line. -P runs N processes in parallel for significant speedups on I/O-bound tasks.

Split Output to File and Stdout

syntax
tee [options] file...
example
make build 2>&1 | tee build.log
echo 'new entry' | sudo tee -a /etc/hosts

Note -a appends instead of overwriting. tee is essential for writing to root-owned files because 'sudo echo x > /root/file' fails (the redirect runs as your user, not root). Use 'echo x | sudo tee /root/file' instead.

Format Tabular Output

syntax
column [options]
example
cat data.csv | column -t -s','
mount | column -t
output
name     age  department
Alice    30   Engineering
Bob      25   Marketing

Note -t creates a neatly aligned table. -s sets the input delimiter. Great for making CSV data or command output readable in the terminal.

Merge Lines Side by Side

syntax
paste [options] file1 file2...
example
paste -d',' names.txt scores.txt
seq 10 | paste - - - -
output
Alice,95
Bob,87
Carol,92

Note -d sets the delimiter (default is tab). Using - multiple times reads successive lines from stdin into columns. The seq example arranges 1-10 into a 4-column layout.

Networking

Transfer Data with URLs

syntax
curl [options] URL
example
curl https://api.example.com/users
curl -X POST -H 'Content-Type: application/json' -d '{"name":"Ada"}' https://api.example.com/users
curl -o release.tar.gz -L https://github.com/proj/repo/archive/v2.1.tar.gz

Note -o saves to a file, -O uses the remote filename. -L follows redirects (essential for GitHub downloads). -s silences progress bar. -I fetches only headers. -k skips TLS verification (use only for debugging, never in production).

Download Files

syntax
wget [options] URL
example
wget https://releases.example.com/v3.1/app.deb
wget -c https://dumps.example.com/backup.sql.gz
wget -r -np -l 2 https://docs.example.com/manual/

Note -c resumes a partially downloaded file. -r downloads recursively, -np prevents ascending to the parent directory, -l sets recursion depth. wget is simpler than curl for straightforward downloads and supports resuming by default.

Secure Shell Remote Login

syntax
ssh [options] user@host
example
ssh deploy@10.0.1.50
ssh -i ~/.ssh/prod_key.pem ec2-user@server.example.com
ssh -L 5432:db-host:5432 bastion@jump.example.com

Note -i specifies a private key file. -L sets up local port forwarding (the example tunnels a remote Postgres port to localhost:5432). -p sets a non-standard SSH port. Add -v for verbose debugging of connection issues.

Secure Copy Between Hosts

syntax
scp [options] source destination
example
scp deploy.tar.gz deploy@10.0.1.50:/opt/releases/
scp -r deploy@server:/var/log/app/ ./remote-logs/
scp -P 2222 config.yml user@host:/etc/app/

Note -r copies directories recursively. -P (capital) specifies a non-standard port. For large or repeated transfers, rsync is preferable as it only sends differences.

Efficient File Synchronization

syntax
rsync [options] source destination
example
rsync -avz --progress ./build/ deploy@web:/var/www/app/
rsync -avz --delete --exclude='.git' src/ backup/src/
rsync -avz -e 'ssh -p 2222' data/ user@host:/data/

Note -a is archive mode (preserves permissions, timestamps, symlinks). -v is verbose. -z compresses during transfer. --delete removes files from the destination that no longer exist in the source (be careful). Trailing slash on source matters: dir/ syncs contents, dir syncs the directory itself.

Test Network Connectivity

syntax
ping [options] host
example
ping -c 5 google.com
ping -c 3 192.168.1.1
output
64 bytes from 142.250.80.46: icmp_seq=1 ttl=118 time=11.2 ms
--- google.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss

Note -c limits the number of pings (without it, Linux pings forever; macOS defaults to unlimited too). High time values indicate latency; packet loss indicates connectivity problems.

View Network Connections & Ports

syntax
ss [options]
netstat [options]
example
ss -tlnp
ss -s
netstat -tlnp
output
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN  0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1482,fd=6))

Note ss is the modern replacement for netstat. -t shows TCP, -u shows UDP, -l shows listening sockets, -n shows numeric ports (not names), -p shows the process using it. netstat is deprecated on many systems but still widely used.

Netcat: Network Swiss Army Knife

syntax
nc [options] host port
example
nc -zv server.example.com 443
echo 'PING' | nc -w 2 redis.local 6379
nc -l 9090
output
Connection to server.example.com 443 port [tcp/https] succeeded!

Note -z scans without sending data (port check). -v is verbose. -w sets a timeout in seconds. -l listens on a port (useful for quick debugging). Netcat is invaluable for testing whether a port is open from a particular host.

DNS Lookup

syntax
dig [options] domain [type]
example
dig example.com
dig example.com MX +short
dig @8.8.8.8 example.com A +trace
output
example.com.  300  IN  A  93.184.216.34

Note +short gives concise output. @server queries a specific DNS server. Common types: A (IPv4), AAAA (IPv6), MX (mail), CNAME (alias), TXT, NS. +trace follows the full delegation chain from root servers.

View & Configure Network Interfaces

syntax
ip addr show
ifconfig
example
ip addr show eth0
ip route show
ifconfig en0
output
2: eth0: <BROADCAST,MULTICAST,UP> mtu 1500
    inet 192.168.1.42/24 brd 192.168.1.255 scope global eth0

Note ip is the modern Linux tool; ifconfig is legacy but still default on macOS. 'ip addr' shows addresses, 'ip route' shows the routing table, 'ip link' manages interfaces. On macOS, the primary interface is usually en0.

Archives & Compression

Create a Tar Archive

syntax
tar -cf archive.tar files...
tar -czf archive.tar.gz files...
example
tar -czf backup_20260404.tar.gz /opt/app/data/ /opt/app/config/
tar -cjf source.tar.bz2 --exclude='.git' --exclude='node_modules' project/

Note -c creates, -z compresses with gzip, -j with bzip2, -J with xz. -f must be followed by the archive name. --exclude omits patterns. Always put -f last among the single-letter flags or use the long form to avoid mistakes.

Extract a Tar Archive

syntax
tar -xf archive.tar [-C directory]
example
tar -xzf backup_20260404.tar.gz -C /opt/restore/
tar -xf release.tar.bz2
tar -tf archive.tar.gz

Note -x extracts, -C specifies the target directory. tar auto-detects compression on modern systems, so -xf often works without specifying -z/-j/-J. -t lists contents without extracting (always do this first on untrusted archives). WARNING: Tar archives can contain absolute paths or ../ that overwrite files outside the target; use -t to inspect first.

Gzip Compress & Decompress

syntax
gzip [options] file
gunzip file.gz
example
gzip access.log
gunzip access.log.gz
gzip -k -9 database.sql

Note gzip replaces the original file by default. -k keeps the original. -9 uses maximum compression (slower). -d decompresses (same as gunzip). gzip compresses single files; combine with tar for directories.

Zip Archive

syntax
zip [options] archive.zip files...
unzip [options] archive.zip
example
zip -r project.zip project/ -x '*.git*'
unzip project.zip -d /opt/builds/
unzip -l project.zip

Note -r is required for directories. -x excludes patterns. unzip -l lists contents without extracting. -d specifies the extraction directory. zip is the most portable format for sharing with Windows and macOS users.

Bzip2 Compression

syntax
bzip2 [options] file
bunzip2 file.bz2
example
bzip2 -k large_dataset.csv
bunzip2 archive.bz2

Note Bzip2 achieves better compression ratios than gzip but is slower. -k keeps the original file. Like gzip, it handles single files; pair with tar for directories.

XZ Compression

syntax
xz [options] file
unxz file.xz
example
xz -9 -k firmware.bin
tar -cJf logs.tar.xz /var/log/app/
unxz data.xz

Note xz provides the best compression ratio among common tools but uses the most CPU and memory. -9 is maximum compression. Often used for distributing large software packages (e.g., Linux kernel tarballs).

Read Compressed Files Without Extracting

syntax
zcat file.gz
zgrep pattern file.gz
example
zcat access.log.2.gz | tail -100
zgrep 'error 500' /var/log/nginx/access.log.*.gz

Note zcat works like cat on gzipped files. zgrep searches inside gzipped files. zless lets you page through them. These save time and disk space when investigating rotated log files.

Bash Scripting

Variables & Quoting

syntax
name=value
$name or ${name}
"double quotes" vs 'single quotes'
example
APP_ENV="production"
PORT=8080
echo "Server running on port ${PORT} in ${APP_ENV}"
FILES=$(ls *.conf)
READONLY_VAR="locked"
readonly READONLY_VAR
output
Server running on port 8080 in production

Note No spaces around the = sign. Double quotes expand variables; single quotes are literal. Always double-quote variable expansions ("$var") to prevent word splitting and glob expansion. Use $() for command substitution instead of backticks.

If / Else Conditional

syntax
if [[ condition ]]; then
  commands
elif [[ condition ]]; then
  commands
else
  commands
fi
example
if [[ -f "/opt/app/config.yml" ]]; then
  echo "Config found"
elif [[ -f "/etc/app/config.yml" ]]; then
  echo "Using system config"
else
  echo "No config found, using defaults"
  exit 1
fi

Note Use [[ ]] (double bracket) over [ ] for safer string comparisons and pattern matching. Common file tests: -f (file exists), -d (directory exists), -r (readable), -z (string is empty), -n (string is not empty). Use && and || inside [[ ]].

For Loop

syntax
for var in list; do
  commands
done
example
for host in web01 web02 web03; do
  echo "Deploying to ${host}..."
  scp app.tar.gz deploy@"${host}":/opt/releases/
done

# C-style:
for ((i=1; i<=5; i++)); do
  echo "Attempt ${i}"
done
output
Deploying to web01...
Deploying to web02...
Deploying to web03...

Note Never parse ls output in a for loop. Use globs instead: for f in *.log; do ... done. For iterating over lines in a file: while IFS= read -r line; do ... done < file.txt.

While Loop

syntax
while [[ condition ]]; do
  commands
done
example
RETRIES=0
while [[ $RETRIES -lt 5 ]]; do
  if curl -sf http://localhost:8080/health; then
    echo "Service is healthy"
    break
  fi
  echo "Retry ${RETRIES}..."
  sleep 2
  ((RETRIES++))
done

# Read file line by line:
while IFS= read -r line; do
  echo "Processing: ${line}"
done < servers.txt

Note break exits the loop. continue skips to the next iteration. The 'read file line by line' pattern is the correct way to process files; for loops split on whitespace, which causes bugs with filenames containing spaces.

Case Statement

syntax
case $variable in
  pattern1) commands ;;
  pattern2) commands ;;
  *) default ;;
esac
example
case "$1" in
  start)
    echo "Starting service..."
    systemctl start myapp
    ;;
  stop|restart)
    echo "${1}ing service..."
    systemctl "$1" myapp
    ;;
  status)
    systemctl status myapp
    ;;
  *)
    echo "Usage: $0 {start|stop|restart|status}"
    exit 1
    ;;
esac

Note Each branch ends with ;;. Patterns support globbing and | for alternatives. case is cleaner than long if/elif chains when matching a single variable against many values. *) is the default/catch-all branch.

Functions

syntax
function_name() {
  commands
  return exit_code
}
example
log_msg() {
  local level="$1"
  local message="$2"
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] ${message}"
}

log_msg "INFO" "Deployment started"
log_msg "ERROR" "Config file missing"
output
[2026-04-04 14:22:01] [INFO] Deployment started
[2026-04-04 14:22:01] [ERROR] Config file missing

Note Use local to scope variables to the function (without local, variables are global). Arguments are accessed via $1, $2, etc. $@ is all arguments. return sets the exit code (0-255); to return strings, echo them and capture with $().

Exit Codes & Error Handling

syntax
$?
set -e
set -o pipefail
command || handle_error
example
#!/usr/bin/env bash
set -euo pipefail

grep -q 'ready' status.txt || { echo 'Not ready'; exit 1; }

if ! deploy_app; then
  echo "Deploy failed with code $?"
  rollback
  exit 1
fi

Note $? holds the exit code of the last command (0 = success, non-zero = failure). set -e aborts the script on any non-zero exit. set -u treats unset variables as errors. set -o pipefail catches failures in piped commands. Always use all three in production scripts.

Trap Signals for Cleanup

syntax
trap 'commands' SIGNAL...
example
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"; echo "Cleaned up temp files"' EXIT

# Handle Ctrl+C gracefully:
trap 'echo "Interrupted!"; exit 130' INT

Note EXIT runs on any script exit (normal or error). INT catches Ctrl+C. TERM catches kill signals. trap is essential for cleaning up temp files, releasing locks, or restoring state. Multiple traps on the same signal replace the previous one.

Arrays

syntax
arr=(val1 val2 val3)
${arr[0]}  ${arr[@]}  ${#arr[@]}
example
SERVERS=("web01.prod" "web02.prod" "db01.prod")
echo "First: ${SERVERS[0]}"
echo "All: ${SERVERS[@]}"
echo "Count: ${#SERVERS[@]}"

SERVERS+=("cache01.prod")

for srv in "${SERVERS[@]}"; do
  ssh deploy@"${srv}" 'uptime'
done
output
First: web01.prod
All: web01.prod web02.prod db01.prod
Count: 3

Note Indices start at 0. Always quote "${arr[@]}" to preserve elements with spaces. ${#arr[@]} gives the length. += appends. unset 'arr[1]' removes an element (but does not reindex). Bash arrays are not available in sh/POSIX shell.

String Operations

syntax
${#string}          # length
${string:offset:length}  # substring
${string/pattern/replacement}
example
FILEPATH="/opt/app/logs/server.log"
echo "Length: ${#FILEPATH}"
echo "Filename: ${FILEPATH##*/}"
echo "Directory: ${FILEPATH%/*}"
echo "Change ext: ${FILEPATH%.log}.txt"

URL="https://api.example.com"
echo "${URL/#https/http}"
output
Length: 26
Filename: server.log
Directory: /opt/app/logs
Change ext: /opt/app/logs/server.txt
http://api.example.com

Note ## removes longest prefix match, # removes shortest. %% removes longest suffix, % removes shortest. These are pure Bash operations (no subprocess), so they are much faster than calling sed or basename in a loop.

Redirects & Pipes

Redirect Standard Output

syntax
command > file
command >> file
example
echo 'server.port=8080' > app.properties
date >> deployment.log
psql -c 'SELECT * FROM users' > users_export.csv

Note > overwrites the file completely. >> appends to the end. WARNING: Redirecting to a file you are also reading from (e.g., sort file > file) will truncate it to zero bytes. Use a temporary file or sort -o file file instead.

Redirect Standard Error

syntax
command 2> file
command 2>> file
command 2>/dev/null
example
find / -name 'pg_hba.conf' 2>/dev/null
gcc main.c 2> compile_errors.log

Note File descriptor 1 is stdout, 2 is stderr. 2>/dev/null discards error messages (useful for suppressing 'Permission denied' noise from find). 2>> appends errors to a file.

Combine Stdout & Stderr

syntax
command > file 2>&1
command &> file
command 2>&1 | other
example
make build > build.log 2>&1
./run_tests.sh &> test_results.log
curl https://api.example.com 2>&1 | tee response.log

Note 2>&1 means 'send stderr to wherever stdout is going'. Order matters: > file 2>&1 works (redirect stdout to file, then stderr to stdout's destination). 2>&1 > file does NOT capture stderr to the file. &> is a Bash shorthand for both.

Pipes

syntax
command1 | command2 | command3
example
cat access.log | grep 'POST' | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
output
    847 /api/users
    623 /api/orders
    412 /api/auth/login

Note Each | sends the stdout of the left command to the stdin of the right command. If any command in the middle fails, data just stops flowing. Use set -o pipefail in scripts to catch errors in any part of the pipe, not just the last command.

/dev/null: The Data Black Hole

syntax
command > /dev/null
command > /dev/null 2>&1
example
if command -v docker > /dev/null 2>&1; then
  echo 'Docker is installed'
fi
crontab_job: */5 * * * * /opt/scripts/cleanup.sh > /dev/null 2>&1

Note /dev/null discards anything written to it. Redirecting both stdout and stderr to /dev/null completely silences a command. Common in cron jobs and conditional checks where you care about the exit code, not the output.

Here Document

syntax
command <<DELIMITER
text
DELIMITER
example
cat <<EOF > /etc/nginx/conf.d/app.conf
server {
    listen 80;
    server_name app.example.com;
    location / {
        proxy_pass http://localhost:3000;
    }
}
EOF

# Suppress variable expansion:
cat <<'EOF'
Use $HOME to reference your home directory
EOF

Note Variables and commands are expanded inside a here document by default. Quoting the delimiter ('EOF') disables expansion, which is useful when writing scripts or config files that contain $ characters. <<- strips leading tabs (not spaces) for cleaner indentation.

Here String

syntax
command <<< "string"
example
grep 'error' <<< "$LOG_OUTPUT"
bc <<< '2.5 * 3.7'
read -r first last <<< "Ada Lovelace"
output
9.25

Note Here strings feed a string directly to a command's stdin without needing echo and a pipe. More concise than echo "string" | command. A Bash feature not available in POSIX sh.

Process Substitution

syntax
<(command)
>(command)
example
diff <(ssh web01 'cat /etc/nginx/nginx.conf') <(ssh web02 'cat /etc/nginx/nginx.conf')
paste <(cut -d',' -f1 data.csv) <(cut -d',' -f3 data.csv)

Note <(command) creates a virtual file containing the command's output. This allows commands that require filename arguments to read from a pipeline. Works only in Bash and Zsh, not POSIX sh.

Tee: Save & Pass Through

syntax
command | tee file | next_command
example
deploy.sh 2>&1 | tee deploy_$(date +%Y%m%d).log | grep -E 'ERROR|WARNING'

Note tee writes its input to a file and also passes it through to stdout. This lets you save intermediate pipeline data while still processing it downstream. Use -a to append rather than overwrite the file.

System Info

System & Kernel Information

syntax
uname [options]
example
uname -a
uname -r
uname -s
output
Linux web01 5.15.0-94-generic #104-Ubuntu SMP x86_64 GNU/Linux

Note -a shows all info. -r shows kernel release. -s shows kernel name. -m shows architecture (x86_64, aarch64). Useful in scripts that need to detect the OS: if [[ $(uname -s) == 'Darwin' ]]; then ... (macOS) fi.

Disk Space Usage

syntax
df [options] [filesystem]
example
df -h
df -h /
df -hT
output
Filesystem     Type  Size  Used Avail Use% Mounted on
/dev/sda1      ext4   50G   32G   16G  67% /
/dev/sdb1      xfs   200G  145G   55G  73% /data

Note -h for human-readable sizes. -T shows filesystem type. -i shows inode usage (you can run out of inodes even with free space if you have millions of tiny files). Check df regularly on servers to prevent disk-full outages.

Directory Size

syntax
du [options] [directory]
example
du -sh /var/log/
du -h --max-depth=1 /opt/ | sort -rh
du -sh ~/projects/*
output
2.3G    /var/log/
1.1G    /opt/app
450M    /opt/data
32M     /opt/config

Note -s shows total for each argument (summary). -h is human-readable. --max-depth limits how deep to report. Combining with sort -rh quickly identifies what is consuming the most space.

Memory Usage

syntax
free [options]
example
free -h
output
              total   used   free   shared  buff/cache  available
Mem:          16Gi   5.2Gi  2.1Gi  312Mi    8.7Gi       10Gi
Swap:         4.0Gi  128Mi  3.9Gi

Note -h for human-readable. The 'available' column is the best indicator of how much memory is actually free for applications (it includes reclaimable buffer/cache). Linux aggressively caches, so low 'free' is normal and does not mean you are out of memory. Not available on macOS; use vm_stat instead.

System Uptime & Load

syntax
uptime
example
uptime
output
 14:22:01 up 47 days, 3:15,  2 users,  load average: 0.52, 0.78, 0.65

Note Load averages are for the last 1, 5, and 15 minutes. On a single-core system, a load of 1.0 means it is fully utilized. On a 4-core system, a load of 4.0 is fully utilized. Consistently high load relative to core count indicates the server needs investigation.

Current User Info

syntax
whoami
id [user]
example
whoami
id
id deploy
output
deploy
uid=1000(deploy) gid=1000(deploy) groups=1000(deploy),27(sudo),999(docker)

Note whoami prints just the username. id shows uid, gid, and all group memberships. Useful in scripts to verify you are running as the expected user: if [[ $(whoami) != 'root' ]]; then echo 'Run as root'; exit 1; fi.

Environment Variables

syntax
env
export VAR=value
printenv VAR
example
env | grep PATH
export DATABASE_URL='postgres://user:pass@db:5432/mydb'
printenv HOME
output
/home/deploy

Note env lists all environment variables. export makes a variable available to child processes. Variables set without export are local to the current shell. printenv retrieves a single variable. Avoid putting secrets in environment variables on shared systems; prefer a secrets manager.

Date & Time

syntax
date [+format]
example
date
date '+%Y-%m-%d %H:%M:%S'
date -d '+7 days' '+%Y-%m-%d'
date -u
output
Sat Apr  4 14:22:01 UTC 2026
2026-04-04 14:22:01
2026-04-11

Note Common format tokens: %Y (year), %m (month), %d (day), %H (hour), %M (minute), %S (second), %s (Unix epoch). -d adjusts the date (GNU only; on macOS use -v). -u outputs UTC. Useful for timestamped filenames: backup_$(date +%Y%m%d_%H%M%S).tar.gz.

CPU Information

syntax
lscpu
example
lscpu
output
Architecture:        x86_64
CPU(s):              8
Model name:          Intel(R) Core(TM) i7-10700 @ 2.90GHz
Thread(s) per core:  2
Core(s) per socket:  4

Note Shows CPU architecture, core count, threads, cache sizes, and more. On macOS, use sysctl -n machdep.cpu.brand_string and sysctl -n hw.ncpu instead.

List Block Devices

syntax
lsblk [options]
example
lsblk -f
output
NAME   FSTYPE  LABEL   SIZE MOUNTPOINT
sda                    100G
├─sda1 ext4   root     50G /
├─sda2 swap            4G  [SWAP]
└─sda3 xfs    data    46G  /data

Note -f shows filesystem type, label, UUID, and mount points. Useful for identifying disks before mounting or partitioning. Linux only; on macOS, use diskutil list.

Package Management

APT (Debian, Ubuntu)

syntax
sudo apt update
sudo apt install package
sudo apt remove package
example
sudo apt update && sudo apt upgrade -y
sudo apt install nginx postgresql-16
sudo apt search image-editor
apt list --installed | grep python

Note Always run apt update before installing to refresh package lists. apt is for Debian-based distributions (Debian, Ubuntu, Linux Mint, Pop!_OS). -y auto-confirms prompts. Use apt autoremove to clean up unused dependencies.

Homebrew (macOS, Linux)

syntax
brew install formula
brew install --cask app
brew update && brew upgrade
example
brew install jq ripgrep tree
brew install --cask visual-studio-code
brew list
brew cleanup

Note Homebrew is the dominant package manager on macOS and also runs on Linux. Formulae are CLI tools; casks are GUI applications. Run brew update to refresh, brew upgrade to update all installed packages. brew cleanup removes old versions to free space.

YUM / DNF (RHEL, Fedora, CentOS)

syntax
sudo dnf install package
sudo yum install package
example
sudo dnf update
sudo dnf install httpd mariadb-server
sudo dnf search editor
sudo dnf remove old-package

Note dnf is the modern replacement for yum (Fedora 22+, RHEL 8+, CentOS Stream). yum still works on older CentOS/RHEL 7. Both resolve dependencies automatically. Use dnf list installed to see what is installed.

Snap (Cross-Distribution)

syntax
sudo snap install package
snap list
example
sudo snap install code --classic
sudo snap install node --channel=20/stable
snap list
sudo snap refresh

Note --classic is required for snaps that need broader system access (like editors and compilers). Snaps auto-update in the background. Available on Ubuntu by default and installable on most Linux distributions. snap refresh manually triggers updates.

Locate Installed Binaries

syntax
which command
whereis command
command -v command
example
which python3
whereis gcc
command -v node || echo 'node not found'
output
/usr/bin/python3
gcc: /usr/bin/gcc /usr/lib/gcc /usr/share/man/man1/gcc.1.gz

Note which shows the full path of a command. whereis also shows man pages and source locations. In scripts, prefer command -v over which because it is POSIX-compliant and handles aliases/builtins correctly.

Shortcuts & Productivity

Reverse History Search

syntax
Ctrl+R, then type to search
example
# Press Ctrl+R, then type 'docker'
(reverse-i-search)`docker': docker compose up -d --build

Note Press Ctrl+R again to cycle through older matches. Enter executes the found command. Ctrl+G or Esc cancels the search. This is one of the biggest timesavers in daily terminal work.

History Expansion: !! and !$

syntax
!!        # last command
!$        # last argument of previous command
!n        # command number n
!string   # last command starting with string
example
apt install nginx
sudo !!
# Expands to: sudo apt install nginx

mkdir /opt/new-app
cd !$
# Expands to: cd /opt/new-app

!grep
# Reruns last command that started with 'grep'

Note !! is indispensable when you forget sudo. !$ avoids retyping long paths. Use !:n to reference a specific argument (e.g., !:2 for the second). Add :p to preview without executing: !!:p shows what would run.

Create Command Aliases

syntax
alias name='command'
unalias name
example
alias ll='ls -lah'
alias gs='git status'
alias dc='docker compose'
alias k='kubectl'
alias myip='curl -s ifconfig.me'

Note Aliases defined in the terminal are lost when the shell exits. Add them to ~/.bashrc or ~/.zshrc to make them permanent. Run alias with no arguments to list all current aliases. Use unalias to remove one.

Shell Configuration Files

syntax
~/.bashrc  (Bash interactive)
~/.zshrc   (Zsh interactive)
~/.profile (login shells)
example
# Add to ~/.bashrc or ~/.zshrc:
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=vim
alias deploy='cd ~/projects/main && ./deploy.sh'

# Reload after editing:
source ~/.bashrc

Note .bashrc runs for every new interactive Bash shell. .bash_profile runs only for login shells. .zshrc runs for every interactive Zsh shell. After editing, run source ~/.bashrc (or source ~/.zshrc) to apply changes without opening a new terminal.

Brace Expansion

syntax
{a,b,c}
{start..end}
{start..end..step}
example
mkdir -p project/{src,tests,docs}
cp config.yml{,.bak}
echo {1..10}
echo {01..12}
touch report_{Q1,Q2,Q3,Q4}_2026.pdf
output
1 2 3 4 5 6 7 8 9 10
01 02 03 04 05 06 07 08 09 10 11 12

Note config.yml{,.bak} expands to 'config.yml config.yml.bak', making a quick backup. Brace expansion happens before variable expansion, so you cannot use variables inside braces. Ranges support zero-padding ({01..12}) and steps ({0..20..5}).

Command Substitution

syntax
$(command)
`command` (legacy)
example
echo "Today is $(date '+%A, %B %d')"
BRANCH=$(git rev-parse --abbrev-ref HEAD)
FILES_CHANGED=$(git diff --name-only | wc -l)
echo "${FILES_CHANGED} files changed on ${BRANCH}"
output
Today is Saturday, April 04
3 files changed on feature/auth

Note Always use $() instead of backticks. $() nests cleanly: $(echo $(whoami)) works; backticks require escaping for nesting. The output has trailing newlines stripped automatically.

CDPATH: Quick Directory Navigation

syntax
export CDPATH=.:dir1:dir2
example
# Add to ~/.bashrc or ~/.zshrc:
export CDPATH=".:$HOME/projects:$HOME/work"

# Now from anywhere:
cd web-app
# Jumps to ~/projects/web-app without typing the full path

Note CDPATH tells cd to search additional base directories. The leading . ensures the current directory is still checked first. Separate paths with colons. This drastically reduces typing for developers who frequently switch between project directories.

Essential Keyboard Shortcuts

syntax
Ctrl+A / Ctrl+E
Ctrl+U / Ctrl+K
Ctrl+W
Ctrl+L
Ctrl+C / Ctrl+D
example
Ctrl+Amove cursor to beginning of line
Ctrl+Emove cursor to end of line
Ctrl+Udelete from cursor to beginning
Ctrl+Kdelete from cursor to end
Ctrl+Wdelete previous word
Ctrl+Lclear screen (same as clear)
Ctrl+Ccancel current command
Ctrl+Dexit shell (or send EOF)
Ctrl+Zsuspend foreground process
Alt+.   → insert last argument from previous command

Note These work in both Bash and Zsh and are based on Emacs keybindings (the default). Alt+. is one of the most underused shortcuts; it cycles through previous commands' last arguments. Use set -o vi in your shell config to switch to Vim-style keybindings.

Tab Completion

syntax
Tab       # complete
Tab Tab   # show all possibilities
example
cd /etc/ng<Tab>
# Completes to: cd /etc/nginx/

git che<Tab><Tab>
# Shows: checkout  cherry  cherry-pick

Note Tab completion works for commands, file paths, and (with bash-completion or zsh plugins) for subcommands and flags of many tools including git, docker, kubectl, and ssh hosts. Install bash-completion for enhanced support in Bash.