Change Directory
cd [directory]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.
12 sections · 107 entries
cat [options] file...cat server.log
cat -n deploy.sh
cat header.html body.html footer.html > page.htmlNote -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.
head [options] filehead -n 20 access.log
head -c 512 firmware.binNote -n specifies number of lines (default 10). -c specifies number of bytes. Useful for quickly inspecting CSV headers or log files.
tail [options] filetail -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.
less [options] fileless +G server.log
less -N config.yamlNote 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.
wc [options] file...wc -l src/*.py
wc -w essay.txt
find . -name '*.go' | xargs wc -l | tail -1 142 387 4821 app.py
38 95 1102 utils.py
180 482 5923 totalNote -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 [options] filesort -u names.txt
sort -t',' -k3 -n sales.csv
du -sh */ | sort -rhNote -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.
uniq [options] [input [output]]sort access.log | uniq -c | sort -rn | head -20 847 GET /api/users
623 GET /api/health
412 POST /api/loginNote 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.
diff [options] file1 file2diff old_config.ini new_config.ini
diff -u main.py main_v2.py > changes.patch
diff -rq dir_a/ dir_b/--- old_config.ini
+++ new_config.ini
@@ -3,4 +3,4 @@
-timeout=30
+timeout=60Note -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.
file [options] file...file mystery_attachment
file -i database.dumpmystery_attachment: PDF document, version 1.7
database.dump: application/octet-stream; charset=binaryNote 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.
stat [options] filestat package.json 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 +0000Note Shows size, permissions (numeric and symbolic), ownership, inode, timestamps (access, modify, change), and more. On macOS, the output format differs slightly from GNU stat.
chmod [options] mode file...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.
chmod [who][+/-/=][permissions] file...chmod u+x build.sh
chmod go-w config.yml
chmod a+r public/favicon.icoNote 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.
chown [options] user[:group] file...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.
chgrp [options] group file...sudo chgrp developers /opt/shared-repo -RNote Equivalent to chown :group. -R applies recursively. You can only change to a group you belong to unless you are root.
umask [mode]umask
umask 0270022Note 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'.
sudo [options] commandsudo 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.
su [options] [user]su - deploy
su -c 'systemctl restart nginx' rootNote 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.
chmod [1|2|4]nnn file
chmod +t directory
chmod g+s directorychmod 1777 /tmp
chmod 2775 /opt/team-share/
chmod u+s /usr/bin/passwdNote 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.
ps [options]ps aux
ps aux | grep '[n]ginx'
ps -ef --forestUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 168940 11788 ? Ss Mar28 0:09 /sbin/initNote 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.
top
htoptop -o %MEM
htop -u deployNote 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.
kill [signal] PID...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.
killall [options] namekillall -TERM node
killall -u deploy python3Note 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.
command &
bg [%job]
fg [%job]
jobspython3 train_model.py &
jobs
fg %1[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.
nohup command [args] &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.
pgrep [options] pattern
pkill [options] patternpgrep -la nginx
pkill -f 'python3 worker.py'
pgrep -u deploy -c14823 nginx: master process
14824 nginx: worker processNote -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.
lsof [options]lsof -i :3000
lsof -u deploy
lsof +D /var/log/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 [pid|%job]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.
grep [options] pattern [file...]grep -rn 'TODO' src/
grep -i 'error' /var/log/syslog
grep -c 'SELECT' queries.sql
grep -v '^#' nginx.confsrc/api/handler.go:42: // TODO: add rate limiting
src/lib/cache.ts:18: // TODO: implement TTLNote -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.
sed [options] 's/pattern/replacement/flags' filesed '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.txtNote -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.
awk 'pattern { action }' fileawk '{print $1, $4}' access.log
awk -F',' '$3 > 1000 {print $1, $3}' sales.csv
awk '{sum += $5} END {print "Total:", sum}' report.tsv192.168.1.40 [28/Mar/2026:10:15:23
Alice 2340
Total: 87450Note 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.
cut [options] filecut -d',' -f1,3 employees.csv
cut -c1-10 logfile.txtname,department
Alice,Engineering
Bob,MarketingNote -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.
tr [options] set1 [set2]echo 'Hello World' | tr 'A-Z' 'a-z'
cat data.csv | tr ',' '\t'
tr -d '\r' < windows_file.txt > unix_file.txthello worldNote 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.
xargs [options] commandfind . -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.
tee [options] file...make build 2>&1 | tee build.log
echo 'new entry' | sudo tee -a /etc/hostsNote -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.
column [options]cat data.csv | column -t -s','
mount | column -tname age department
Alice 30 Engineering
Bob 25 MarketingNote -t creates a neatly aligned table. -s sets the input delimiter. Great for making CSV data or command output readable in the terminal.
paste [options] file1 file2...paste -d',' names.txt scores.txt
seq 10 | paste - - - -Alice,95
Bob,87
Carol,92Note -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.
curl [options] URLcurl 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.gzNote -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).
wget [options] URLwget 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.
ssh [options] user@hostssh 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.comNote -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.
scp [options] source destinationscp 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.
rsync [options] source destinationrsync -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.
ping [options] hostping -c 5 google.com
ping -c 3 192.168.1.164 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 lossNote -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.
ss [options]
netstat [options]ss -tlnp
ss -s
netstat -tlnpState 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.
nc [options] host portnc -zv server.example.com 443
echo 'PING' | nc -w 2 redis.local 6379
nc -l 9090Connection 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.
dig [options] domain [type]dig example.com
dig example.com MX +short
dig @8.8.8.8 example.com A +traceexample.com. 300 IN A 93.184.216.34Note +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.
ip addr show
ifconfigip addr show eth0
ip route show
ifconfig en02: eth0: <BROADCAST,MULTICAST,UP> mtu 1500
inet 192.168.1.42/24 brd 192.168.1.255 scope global eth0Note 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.
tar -cf archive.tar files...
tar -czf archive.tar.gz files...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.
tar -xf archive.tar [-C directory]tar -xzf backup_20260404.tar.gz -C /opt/restore/
tar -xf release.tar.bz2
tar -tf archive.tar.gzNote -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 [options] file
gunzip file.gzgzip access.log
gunzip access.log.gz
gzip -k -9 database.sqlNote 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 [options] archive.zip files...
unzip [options] archive.zipzip -r project.zip project/ -x '*.git*'
unzip project.zip -d /opt/builds/
unzip -l project.zipNote -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 [options] file
bunzip2 file.bz2bzip2 -k large_dataset.csv
bunzip2 archive.bz2Note 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 [options] file
unxz file.xzxz -9 -k firmware.bin
tar -cJf logs.tar.xz /var/log/app/
unxz data.xzNote 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).
zcat file.gz
zgrep pattern file.gzzcat access.log.2.gz | tail -100
zgrep 'error 500' /var/log/nginx/access.log.*.gzNote 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.
name=value
$name or ${name}
"double quotes" vs 'single quotes'APP_ENV="production"
PORT=8080
echo "Server running on port ${PORT} in ${APP_ENV}"
FILES=$(ls *.conf)
READONLY_VAR="locked"
readonly READONLY_VARServer running on port 8080 in productionNote 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 [[ condition ]]; then
commands
elif [[ condition ]]; then
commands
else
commands
fiif [[ -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
fiNote 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 var in list; do
commands
donefor 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}"
doneDeploying 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 [[ condition ]]; do
commands
doneRETRIES=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.txtNote 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 $variable in
pattern1) commands ;;
pattern2) commands ;;
*) default ;;
esaccase "$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
;;
esacNote 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.
function_name() {
commands
return exit_code
}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"[2026-04-04 14:22:01] [INFO] Deployment started
[2026-04-04 14:22:01] [ERROR] Config file missingNote 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 $().
$?
set -e
set -o pipefail
command || handle_error#!/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
fiNote $? 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 'commands' SIGNAL...TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"; echo "Cleaned up temp files"' EXIT
# Handle Ctrl+C gracefully:
trap 'echo "Interrupted!"; exit 130' INTNote 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.
arr=(val1 val2 val3)
${arr[0]} ${arr[@]} ${#arr[@]}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'
doneFirst: web01.prod
All: web01.prod web02.prod db01.prod
Count: 3Note 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} # length
${string:offset:length} # substring
${string/pattern/replacement}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}"Length: 26
Filename: server.log
Directory: /opt/app/logs
Change ext: /opt/app/logs/server.txt
http://api.example.comNote ## 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.
command > file
command >> fileecho 'server.port=8080' > app.properties
date >> deployment.log
psql -c 'SELECT * FROM users' > users_export.csvNote > 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.
command 2> file
command 2>> file
command 2>/dev/nullfind / -name 'pg_hba.conf' 2>/dev/null
gcc main.c 2> compile_errors.logNote 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.
command > file 2>&1
command &> file
command 2>&1 | othermake build > build.log 2>&1
./run_tests.sh &> test_results.log
curl https://api.example.com 2>&1 | tee response.logNote 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.
command1 | command2 | command3cat access.log | grep 'POST' | awk '{print $7}' | sort | uniq -c | sort -rn | head -10 847 /api/users
623 /api/orders
412 /api/auth/loginNote 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.
command > /dev/null
command > /dev/null 2>&1if command -v docker > /dev/null 2>&1; then
echo 'Docker is installed'
fi
crontab_job: */5 * * * * /opt/scripts/cleanup.sh > /dev/null 2>&1Note /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.
command <<DELIMITER
text
DELIMITERcat <<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
EOFNote 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.
command <<< "string"grep 'error' <<< "$LOG_OUTPUT"
bc <<< '2.5 * 3.7'
read -r first last <<< "Ada Lovelace"9.25Note 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.
<(command)
>(command)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.
command | tee file | next_commanddeploy.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.
uname [options]uname -a
uname -r
uname -sLinux web01 5.15.0-94-generic #104-Ubuntu SMP x86_64 GNU/LinuxNote -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.
df [options] [filesystem]df -h
df -h /
df -hTFilesystem Type Size Used Avail Use% Mounted on
/dev/sda1 ext4 50G 32G 16G 67% /
/dev/sdb1 xfs 200G 145G 55G 73% /dataNote -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.
du [options] [directory]du -sh /var/log/
du -h --max-depth=1 /opt/ | sort -rh
du -sh ~/projects/*2.3G /var/log/
1.1G /opt/app
450M /opt/data
32M /opt/configNote -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.
free [options]free -h total used free shared buff/cache available
Mem: 16Gi 5.2Gi 2.1Gi 312Mi 8.7Gi 10Gi
Swap: 4.0Gi 128Mi 3.9GiNote -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.
uptimeuptime 14:22:01 up 47 days, 3:15, 2 users, load average: 0.52, 0.78, 0.65Note 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.
whoami
id [user]whoami
id
id deploydeploy
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.
env
export VAR=value
printenv VARenv | grep PATH
export DATABASE_URL='postgres://user:pass@db:5432/mydb'
printenv HOME/home/deployNote 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 [+format]date
date '+%Y-%m-%d %H:%M:%S'
date -d '+7 days' '+%Y-%m-%d'
date -uSat Apr 4 14:22:01 UTC 2026
2026-04-04 14:22:01
2026-04-11Note 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.
lscpulscpuArchitecture: x86_64
CPU(s): 8
Model name: Intel(R) Core(TM) i7-10700 @ 2.90GHz
Thread(s) per core: 2
Core(s) per socket: 4Note 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.
lsblk [options]lsblk -fNAME FSTYPE LABEL SIZE MOUNTPOINT
sda 100G
├─sda1 ext4 root 50G /
├─sda2 swap 4G [SWAP]
└─sda3 xfs data 46G /dataNote -f shows filesystem type, label, UUID, and mount points. Useful for identifying disks before mounting or partitioning. Linux only; on macOS, use diskutil list.
sudo apt update
sudo apt install package
sudo apt remove packagesudo apt update && sudo apt upgrade -y
sudo apt install nginx postgresql-16
sudo apt search image-editor
apt list --installed | grep pythonNote 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.
brew install formula
brew install --cask app
brew update && brew upgradebrew install jq ripgrep tree
brew install --cask visual-studio-code
brew list
brew cleanupNote 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.
sudo dnf install package
sudo yum install packagesudo dnf update
sudo dnf install httpd mariadb-server
sudo dnf search editor
sudo dnf remove old-packageNote 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.
sudo snap install package
snap listsudo snap install code --classic
sudo snap install node --channel=20/stable
snap list
sudo snap refreshNote --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.
which command
whereis command
command -v commandwhich python3
whereis gcc
command -v node || echo 'node not found'/usr/bin/python3
gcc: /usr/bin/gcc /usr/lib/gcc /usr/share/man/man1/gcc.1.gzNote 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.
Ctrl+R, then type to search# Press Ctrl+R, then type 'docker'
(reverse-i-search)`docker': docker compose up -d --buildNote 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.
!! # last command
!$ # last argument of previous command
!n # command number n
!string # last command starting with stringapt 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.
alias name='command'
unalias namealias 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.
~/.bashrc (Bash interactive)
~/.zshrc (Zsh interactive)
~/.profile (login shells)# 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 ~/.bashrcNote .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.
{a,b,c}
{start..end}
{start..end..step}mkdir -p project/{src,tests,docs}
cp config.yml{,.bak}
echo {1..10}
echo {01..12}
touch report_{Q1,Q2,Q3,Q4}_2026.pdf1 2 3 4 5 6 7 8 9 10
01 02 03 04 05 06 07 08 09 10 11 12Note 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)
`command` (legacy)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}"Today is Saturday, April 04
3 files changed on feature/authNote Always use $() instead of backticks. $() nests cleanly: $(echo $(whoami)) works; backticks require escaping for nesting. The output has trailing newlines stripped automatically.
export CDPATH=.:dir1:dir2# 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 pathNote 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.
Ctrl+A / Ctrl+E
Ctrl+U / Ctrl+K
Ctrl+W
Ctrl+L
Ctrl+C / Ctrl+DCtrl+A → move cursor to beginning of line
Ctrl+E → move cursor to end of line
Ctrl+U → delete from cursor to beginning
Ctrl+K → delete from cursor to end
Ctrl+W → delete previous word
Ctrl+L → clear screen (same as clear)
Ctrl+C → cancel current command
Ctrl+D → exit shell (or send EOF)
Ctrl+Z → suspend foreground process
Alt+. → insert last argument from previous commandNote 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 # complete
Tab Tab # show all possibilitiescd /etc/ng<Tab>
# Completes to: cd /etc/nginx/
git che<Tab><Tab>
# Shows: checkout cherry cherry-pickNote 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.