Linux Notes
Linux
4 September 2026
Keyboard Shortcuts
Cursor & Line Editing
| Key |
What it does |
| Ctrl+A |
Move cursor to beginning of line |
| Ctrl+E |
Move cursor to end of line |
| Ctrl+B / ← |
Move cursor one character left |
| Ctrl+F / → |
Move cursor one character right |
| Alt+B |
Move cursor one word left |
| Alt+F |
Move cursor one word right |
| Ctrl+W |
Delete word to the left of cursor |
| Ctrl+K |
Cut from cursor to end of line |
| Ctrl+U |
Cut from cursor to beginning of line |
| Ctrl+Y |
Paste (yank) the last cut text |
| Ctrl+T |
Swap the two characters before cursor |
History Navigation
| Key |
What it does |
| Ctrl+P / ↑ |
Previous command in history |
| Ctrl+N / ↓ |
Next command in history |
| Ctrl+R |
Reverse search through history (type to filter) |
| Ctrl+G |
Cancel history search, return to prompt |
| !! |
Repeat last command |
| !$ |
Last argument of previous command |
| !abc |
Run most recent command starting with "abc" |
| history |
Show numbered command history list |
| !123 |
Run command number 123 from history |
Process & Job Control
| Key |
What it does |
| Ctrl+C |
Send SIGINT — interrupt (kill) current process |
| Ctrl+Z |
Suspend current process, send to background |
| Ctrl+D |
Send EOF — closes current terminal/session |
| Ctrl+\\ |
Send SIGQUIT — force quit with core dump |
| fg |
Bring suspended job to foreground |
| bg |
Resume suspended job in background |
| jobs |
List all background/suspended jobs |
| command & |
Run command in background immediately |
Tab Completion & Other Tricks
| Key |
What it does |
| Tab |
Auto-complete command, filename, or path |
| Tab Tab |
Show all possible completions when ambiguous |
| Ctrl+L |
Clear the screen (same as clear) |
| Ctrl+S |
Pause terminal output (scroll lock) |
| Ctrl+Q |
Resume terminal output (unlock scroll) |
| Alt+. |
Insert last argument of previous command |
| Ctrl+X Ctrl+E |
Open current command in $EDITOR for editing |
Quick Reference Commands
Navigation
cd - # go back to previous directory
cd ~ # go to home directory
pwd # print current directory path
ls -lah # list with sizes, hidden files, human-readable
Find & Search
grep -r "text" . # search recursively in current dir
find . -name "*.txt" # find files by name pattern
find . -mmin -60 # files modified in last 60 minutes
which command # show full path of a command
File & Disk
du -sh * # size of each item in current dir
df -h # disk space on all mounted filesystems
stat file.txt # detailed file info (size, permissions, times)
wc -l file.txt # count lines in a file
Process & System
ps aux | grep name # find a running process by name
kill -9 PID # force kill a process by PID
top # live process viewer (q to quit)
free -h # RAM usage in human-readable format
uptime # how long the system has been running
File Permissions & Ownership
Understanding the Permission String
Every file in Linux has a 10-character permission string shown by ls -l. Example: drwxr-xr-x
| Position(s) | Meaning |
| d (pos 1) | File type: d=directory, -=regular file, l=symlink, b=block device, c=char device, p=pipe, s=socket |
| rwx (pos 2-4) | Owner permissions: read, write, execute |
| r-x (pos 5-7) | Group permissions: read, no write, execute |
| r-x (pos 8-10) | Others permissions: read, no write, execute |
| r | Read (4): view file contents / list directory |
| w | Write (2): modify file / create or delete files in directory |
| x | Execute (1): run as program / enter directory (cd) |
chmod — Numeric Mode
Each permission group is a sum of: read=4, write=2, execute=1. Three digits: owner, group, others.
| Command | Result |
| chmod 644 file | Owner rw-, Group r--, Others r-- (standard file) |
| chmod 755 file | Owner rwx, Group r-x, Others r-x (standard executable/dir) |
| chmod 700 file | Owner rwx, Group ---, Others --- (private executable) |
| chmod 600 file | Owner rw-, Group ---, Others --- (private file, e.g. SSH keys) |
| chmod 777 file | Everyone rwx (avoid in production) |
| chmod 400 file | Owner r--, nobody else (read-only, e.g. PEM key files) |
| chmod 664 file | Owner rw-, Group rw-, Others r-- (shared group write) |
| chmod -R 755 dir/ | Apply recursively to directory and all contents |
chmod — Symbolic Mode
Symbolic mode uses u (user/owner), g (group), o (others), a (all). Operators: + add, - remove, = set exactly.
| Command | What it does |
| chmod u+x file | Add execute for owner |
| chmod g-w file | Remove write from group |
| chmod o=r file | Set others to read-only (removes write/execute) |
| chmod a+r file | Add read for everyone |
| chmod ug+rw file | Add read+write for owner and group |
| chmod a-x file | Remove execute from all |
| chmod u=rwx,go=rx file | Owner full, group+others rx (same as 755) |
chown and chgrp
| Command | What it does |
| chown alice file | Change owner to alice |
| chown alice:devs file | Change owner to alice, group to devs |
| chown :devs file | Change group only (same as chgrp devs file) |
| chown -R alice:alice dir/ | Recursively change owner and group |
| chgrp devs file | Change group to devs |
| chown --reference=ref.txt file | Copy ownership from ref.txt to file |
umask — Default Permission Mask
umask subtracts from the maximum permission. Files start at 666, directories at 777. umask 022 gives files 644 and dirs 755.
| Command / Value | What it does |
| umask | Show current umask (e.g. 0022) |
| umask 022 | Files: 644, Dirs: 755 (default on most systems) |
| umask 027 | Files: 640, Dirs: 750 (group readable, others blocked) |
| umask 077 | Files: 600, Dirs: 700 (private: only owner access) |
| umask -S | Show umask in symbolic form (e.g. u=rwx,g=rx,o=rx) |
umask calculation
File max: 666 (rw-rw-rw-)
umask: 022 (----w--w-)
Result: 644 (rw-r--r--)
Dir max: 777 (rwxrwxrwx)
umask: 022 (----w--w-)
Result: 755 (rwxr-xr-x)
Special Permission Bits
| Bit / Command | What it does |
| SUID (4xxx) | Set User ID: executable runs with owner's privileges. Shown as s in owner execute bit. Example: -rwsr-xr-x |
| chmod u+s file | Set SUID on a file |
| chmod 4755 file | Set SUID numerically (4 prefix) |
| SGID (2xxx) | Set Group ID: executable runs with group's privileges. On directories: new files inherit the directory's group. Shown as s in group execute bit. |
| chmod g+s dir/ | Set SGID on a directory |
| chmod 2775 dir/ | Set SGID numerically |
| Sticky bit (1xxx) | On directories: only file owner (or root) can delete their own files. Shown as t in others execute bit. Used on /tmp. |
| chmod +t dir/ | Set sticky bit on directory |
| chmod 1777 /tmp | Standard /tmp permissions (sticky+rwx for all) |
| S vs s / T vs t | Uppercase means the underlying execute bit is NOT set; lowercase means it IS set |
Common Permission Patterns
| Mode | Symbolic | Typical use |
| 600 | rw------- | SSH private keys (~/.ssh/id_ed25519) |
| 644 | rw-r--r-- | Web files, config files, regular documents |
| 700 | rwx------ | Private scripts, ~/.ssh directory |
| 755 | rwxr-xr-x | Executables, public web directories |
| 775 | rwxrwxr-x | Shared group project directories |
| 777 | rwxrwxrwx | World-writable (security risk, avoid) |
| 400 | r-------- | Read-only PEM/key files from AWS etc. |
| 440 | r--r----- | /etc/sudoers |
Process Management
ps — Process Snapshot
| Command | What it does |
| ps aux | All processes: USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND |
| ps aux --sort=-%cpu | Sort by CPU descending |
| ps aux --sort=-%mem | Sort by memory descending |
| ps -ef | Full-format listing (UID PID PPID C STIME TTY TIME CMD) |
| ps -p 1234 | Info for specific PID |
| ps --ppid 1234 | Children of process 1234 |
| ps axjf | Process tree with parent-child indentation |
ps aux column guide: VSZ = virtual memory size (KB), RSS = resident set size / actual RAM (KB), STAT: R=running, S=sleeping, D=uninterruptible sleep, Z=zombie, T=stopped, s=session leader, +=foreground, l=multi-threaded.
Kill Signals
| Signal | Meaning & use |
| kill -1 / SIGHUP | Hangup — reload config without restart (daemons). Also sent when terminal closes. |
| kill -2 / SIGINT | Interrupt — same as Ctrl+C. Graceful stop. |
| kill -3 / SIGQUIT | Quit — like SIGINT but dumps core. Ctrl+\ |
| kill -9 / SIGKILL | Kill immediately — cannot be caught or ignored. Use as last resort. |
| kill -15 / SIGTERM | Terminate gracefully — default signal for kill. Process can clean up. |
| kill -18 / SIGCONT | Continue a stopped process. |
| kill -19 / SIGSTOP | Stop (pause) process — cannot be caught. Like Ctrl+Z but from another process. |
| kill -20 / SIGTSTP | Terminal stop — Ctrl+Z. Can be caught/ignored unlike SIGSTOP. |
| kill PID | Send SIGTERM (15) to PID |
| kill -9 PID | Force-kill PID |
| kill -l | List all signal names and numbers |
| killall nginx | Send SIGTERM to all processes named nginx |
| killall -9 nginx | Force-kill all nginx processes |
nice and renice — Process Priority
Niceness ranges from -20 (highest priority) to 19 (lowest priority). Default is 0. Only root can set negative values.
| Command | What it does |
| nice -n 10 command | Start command with niceness 10 (lower priority) |
| nice -n -5 command | Start command with niceness -5 (higher priority, root only) |
| renice 15 -p 1234 | Change niceness of running process 1234 to 15 |
| renice -5 -u alice | Change niceness of all alice's processes to -5 (root only) |
| ps -o pid,ni,comm | Show PID, niceness, and command name |
Background & Foreground Job Control
| Command / Key | What it does |
| command & | Run command in background from the start |
| Ctrl+Z | Suspend (stop) current foreground process |
| bg | Resume suspended job in background |
| bg %2 | Resume job number 2 in background |
| fg | Bring most recent background job to foreground |
| fg %2 | Bring job 2 to foreground |
| jobs | List all background/stopped jobs with job numbers |
| jobs -l | List jobs with PIDs |
| disown %1 | Remove job 1 from job table (survives terminal close, but no SIGHUP protection) |
| disown -h %1 | Mark job to not receive SIGHUP when terminal closes |
| nohup command & | Run command immune to hangup; output goes to nohup.out |
| nohup command > out.log 2>&1 & | nohup with custom log file |
pgrep and pkill
| Command | What it does |
| pgrep nginx | List PIDs of processes matching "nginx" |
| pgrep -l nginx | List PIDs and names |
| pgrep -u alice | List PIDs of all alice's processes |
| pkill nginx | Send SIGTERM to all processes named nginx |
| pkill -9 nginx | Force-kill all nginx processes |
| pkill -u alice | Kill all processes owned by alice |
| pkill -f "python script.py" | Kill by full command line match (-f matches entire cmd) |
lsof and fuser
| Command | What it does |
| lsof -i :80 | What process is using port 80 |
| lsof -i tcp:443 | What process is using TCP port 443 |
| lsof -p 1234 | All files opened by PID 1234 |
| lsof -u alice | All files opened by user alice |
| lsof /var/log/syslog | Which process has this file open |
| fuser 80/tcp | PID using port 80/tcp |
| fuser -k 80/tcp | Kill process using port 80/tcp |
| fuser /mnt/usb | Which process is using the mount point (preventing unmount) |
/proc Filesystem Quick Reference
| Path | What it contains |
| /proc/PID/cmdline | Full command line of process (null-separated) |
| /proc/PID/status | Human-readable process status (Name, State, Pid, VmRSS etc) |
| /proc/PID/fd/ | Directory of file descriptors (symlinks to open files) |
| /proc/PID/maps | Memory map (shared libraries, stack, heap addresses) |
| /proc/cpuinfo | CPU model, cores, flags |
| /proc/meminfo | RAM stats: MemTotal, MemFree, MemAvailable, Buffers, Cached |
| /proc/loadavg | Load averages (1m, 5m, 15m), running/total threads, last PID |
| /proc/uptime | Seconds since boot, seconds idle |
| /proc/net/tcp | TCP connections in hex format |
Linux Networking Commands
ip — Modern Network Configuration
| Command | What it does |
| ip addr show | Show all interfaces with IP addresses |
| ip addr show eth0 | Show info for eth0 only |
| ip addr add 192.168.1.50/24 dev eth0 | Assign IP to interface (temporary) |
| ip addr del 192.168.1.50/24 dev eth0 | Remove IP from interface |
| ip link show | Show link-layer (MAC, state UP/DOWN) for all interfaces |
| ip link set eth0 up | Bring interface up |
| ip link set eth0 down | Bring interface down |
| ip route show | Show routing table |
| ip route add default via 192.168.1.1 | Add default gateway |
| ip route add 10.0.0.0/8 via 192.168.1.254 | Add static route |
| ip route del 10.0.0.0/8 | Delete route |
| ip neigh show | Show ARP table |
ss — Socket Statistics (replaces netstat)
| Command | What it does |
| ss -tulpn | All TCP/UDP listening ports with process names and PIDs |
| ss -t | All established TCP connections |
| ss -u | UDP sockets |
| ss -l | Listening sockets only |
| ss -p | Show process using the socket |
| ss -n | Numeric (don't resolve names) |
| ss -s | Summary statistics |
| ss -4 / ss -6 | IPv4 only / IPv6 only |
| ss 'sport = :22' | Filter by source port 22 |
Flag breakdown for ss -tulpn: t=TCP, u=UDP, l=listening, p=process, n=numeric
ping, traceroute, mtr
| Command | What it does |
| ping -c 4 8.8.8.8 | Send 4 ICMP echo requests to 8.8.8.8 |
| ping -i 0.2 host | Ping every 0.2 seconds (flood ping: -i 0 needs root) |
| ping -s 1400 host | Send 1400-byte packets (test MTU) |
| ping6 host | Ping via IPv6 |
| traceroute host | Show each hop to destination (uses UDP by default) |
| traceroute -T host | Use TCP SYN packets (better through firewalls) |
| traceroute -I host | Use ICMP echo (like Windows tracert) |
| mtr host | Combines ping + traceroute in real-time display |
| mtr --report host | mtr report mode (non-interactive, good for logging) |
| mtr -n host | mtr without DNS resolution |
curl — HTTP Requests
| Command | What it does |
| curl https://example.com | GET request, print body to stdout |
| curl -I https://example.com | HEAD request — show response headers only |
| curl -L https://example.com | Follow redirects |
| curl -o file.html https://example.com | Save output to file.html |
| curl -O https://example.com/file.zip | Save with remote filename |
| curl -X POST -d 'key=val' URL | POST with form data |
| curl -X POST -H 'Content-Type: application/json' -d '{"key":"val"}' URL | POST JSON body |
| curl -H 'Authorization: Bearer TOKEN' URL | Set custom header |
| curl -u user:pass URL | HTTP Basic Authentication |
| curl -s URL | Silent mode (no progress bar) |
| curl -v URL | Verbose: show request and response headers |
| curl -k URL | Ignore SSL certificate errors |
| curl -x http://proxy:3128 URL | Use HTTP proxy |
| curl --max-time 10 URL | Timeout after 10 seconds |
dig and nslookup — DNS Lookups
| Command | What it does |
| dig example.com | A record lookup (default) |
| dig example.com MX | Mail exchange records |
| dig example.com TXT | TXT records (SPF, DKIM, etc) |
| dig example.com NS | Name server records |
| dig -x 8.8.8.8 | Reverse DNS lookup (PTR record) |
| dig @8.8.8.8 example.com | Query specific DNS server |
| dig +short example.com | Short output — just the answer |
| dig +trace example.com | Trace full DNS resolution from root |
| nslookup example.com | Simple DNS lookup |
| nslookup example.com 1.1.1.1 | Query Cloudflare DNS |
nc (Netcat) and wget
| Command | What it does |
| nc -zv host 80 | Test if port 80 is open (z=scan, v=verbose) |
| nc -zv host 20-80 | Scan port range 20-80 |
| nc -l 4444 | Listen on port 4444 |
| nc host 4444 | Connect to host port 4444 |
| nc -l 4444 > received.txt | Receive file over netcat |
| nc host 4444 < file.txt | Send file over netcat |
| wget https://example.com/file.zip | Download file |
| wget -r -np https://example.com/dir/ | Recursive download (no parent) |
| wget -c URL | Continue interrupted download |
| wget -q URL | Quiet mode |
| wget -O outfile URL | Save to specific filename |
Network Config Files
| File / Path | Purpose |
| /etc/hosts | Static hostname-to-IP mappings. Checked before DNS. Format: 192.168.1.10 myserver |
| /etc/resolv.conf | DNS resolver config. nameserver 8.8.8.8, search example.com, domain example.com |
| /etc/hostname | System hostname |
| /etc/nsswitch.conf | Order of hostname resolution: hosts: files dns means check /etc/hosts first |
| /etc/network/interfaces | Network interface config (Debian/Ubuntu traditional) |
| /etc/netplan/*.yaml | Netplan config (modern Ubuntu) |
Text Processing Tools
grep — Search Text
| Command / Flag | What it does |
| grep "pattern" file | Print lines matching pattern |
| grep -r "pattern" dir/ | Recursive search through directory |
| grep -i "pattern" file | Case-insensitive match |
| grep -v "pattern" file | Invert: lines NOT matching |
| grep -n "pattern" file | Show line numbers |
| grep -c "pattern" file | Count matching lines |
| grep -l "pattern" *.txt | List filenames that contain match (not lines) |
| grep -A 3 "pattern" file | Show 3 lines After each match |
| grep -B 3 "pattern" file | Show 3 lines Before each match |
| grep -C 3 "pattern" file | Show 3 lines Context (before and after) |
| grep -E "pat1|pat2" file | Extended regex: match either pattern |
| grep -P "\d{3}-\d{4}" file | Perl-compatible regex (PCRE) |
| grep -w "word" file | Match whole word only |
| grep -o "pattern" file | Print only the matching part, not the whole line |
| grep -F "literal" file | Fixed string (no regex, faster) |
sed — Stream Editor
| Command | What it does |
| sed 's/old/new/' file | Replace first occurrence per line |
| sed 's/old/new/g' file | Replace all occurrences per line (global) |
| sed 's/old/new/2' file | Replace 2nd occurrence per line |
| sed -i 's/old/new/g' file | Edit file in-place (modifies file directly) |
| sed -i.bak 's/old/new/g' file | Edit in-place, backup original as file.bak |
| sed '5d' file | Delete line 5 |
| sed '/pattern/d' file | Delete all lines matching pattern |
| sed -n '5,10p' file | Print only lines 5-10 (-n suppresses default print) |
| sed -n '/start/,/end/p' file | Print lines from /start/ to /end/ pattern |
| sed '1i\New first line' file | Insert line before line 1 |
| sed '$a\New last line' file | Append line after last line |
| sed 's/\t/ /g' file | Replace tabs with spaces |
| sed '/^$/d' file | Delete blank lines |
| sed 's/^/PREFIX: /' file | Prepend text to every line |
awk — Pattern & Action Processing
| Command | What it does |
| awk '{print $1}' file | Print first field (space-separated by default) |
| awk '{print $NF}' file | Print last field ($NF = number of fields) |
| awk -F: '{print $1}' /etc/passwd | Use : as field delimiter, print first field |
| awk '{print $1, $3}' file | Print fields 1 and 3 (comma adds OFS separator) |
| awk 'NR==5' file | Print only line 5 (NR = record/line number) |
| awk 'NR>=5 && NR<=10' file | Print lines 5 through 10 |
| awk '/pattern/{print}' file | Print lines matching pattern |
| awk '{sum+=$1} END{print sum}' file | Sum first column |
| awk 'BEGIN{print "start"} {print} END{print "end"}' file | Run code before and after processing |
| awk '{print NF}' file | Print number of fields per line |
| awk 'length($0) > 80' file | Print lines longer than 80 chars |
| awk '!seen[$0]++' file | Remove duplicate lines (preserving order) |
cut, sort, uniq
| Command | What it does |
| cut -d: -f1 /etc/passwd | Extract field 1 with : delimiter |
| cut -d, -f2,4 file.csv | Extract fields 2 and 4 from CSV |
| cut -c1-10 file | Extract characters 1-10 from each line |
| sort file | Sort lines alphabetically |
| sort -n file | Sort numerically |
| sort -r file | Reverse order |
| sort -u file | Sort and remove duplicates |
| sort -k2 file | Sort by field 2 |
| sort -k2 -n file | Sort by field 2 numerically |
| sort -t: -k3 -n /etc/passwd | Sort /etc/passwd by UID (field 3) |
| uniq file | Remove consecutive duplicate lines (sort first) |
| uniq -c file | Prefix each line with count of occurrences |
| uniq -d file | Print only duplicate lines |
| uniq -u file | Print only unique lines (no duplicates) |
tr, wc, head, tail, xargs
| Command | What it does |
| tr 'a-z' 'A-Z' < file | Convert lowercase to uppercase |
| tr -d '\r' < file | Delete carriage returns (Windows line endings) |
| tr -s ' ' < file | Squeeze multiple spaces into one |
| tr '[:space:]' '
' < file | Split whitespace into separate lines |
| wc -l file | Count lines |
| wc -w file | Count words |
| wc -c file | Count bytes |
| wc -m file | Count characters (handles multi-byte) |
| head -n 20 file | First 20 lines |
| head -n -5 file | All lines except last 5 |
| tail -n 20 file | Last 20 lines |
| tail -f /var/log/syslog | Follow file as it grows (live log viewing) |
| tail -n +5 file | Print from line 5 onwards (skip first 4) |
| xargs | Build and execute commands from stdin |
| find . -name "*.log" | xargs rm | Delete all .log files found |
| cat list.txt | xargs -I{} cp {} /backup/ | Copy each filename from list.txt to /backup/ |
| xargs -n1 -P4 command | Run command in parallel with 4 processes, 1 arg each |
Practical Pipelines
Common one-liners
# Top 10 most common IPs in access.log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# Count non-empty lines in a file
grep -c . file.txt
# Find lines matching pattern, extract 2nd field
grep "ERROR" app.log | awk '{print $2}' | sort | uniq -c
# Replace all occurrences in multiple files
grep -rl "oldtext" . | xargs sed -i 's/oldtext/newtext/g'
# Show unique HTTP status codes from nginx log
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# Remove blank lines and comment lines (#) from a config file
grep -v '^\s*#' config.conf | grep -v '^\s*$'
# Extract email addresses from a file
grep -oE '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' file.txt
Bash Scripting Reference
Variables and Special Variables
| Variable / Syntax | Meaning |
| VAR=value | Assign variable (no spaces around =) |
| $VAR or ${VAR} | Reference variable value (braces required before text) |
| readonly VAR=value | Make variable read-only (cannot be changed) |
| local VAR=value | Function-scoped variable (only inside function) |
| export VAR=value | Export to environment (child processes inherit it) |
| unset VAR | Delete variable |
| $0 | Script name / path |
| $1 $2 ... $9 | Positional arguments |
| $# | Number of arguments |
| $@ | All arguments as separate words (use in loops) |
| $* | All arguments as single string |
| $? | Exit status of last command (0=success, non-zero=error) |
| $$ | Current shell PID |
| $! | PID of last background command |
| $_ | Last argument of previous command |
| $IFS | Internal Field Separator (default: space, tab, newline) |
String Operations
| Syntax | What it returns |
| ${#VAR} | Length of string in VAR |
| ${VAR:2} | Substring starting at position 2 |
| ${VAR:2:5} | Substring: 5 characters starting at position 2 |
| ${VAR/old/new} | Replace first occurrence of old with new |
| ${VAR//old/new} | Replace all occurrences of old with new |
| ${VAR^^} | Convert to UPPERCASE |
| ${VAR,,} | Convert to lowercase |
| ${VAR:-default} | Use default if VAR is unset or empty |
| ${VAR:=default} | Set VAR to default if unset/empty, then use it |
| ${VAR:?error msg} | Exit with error message if VAR is unset/empty |
| ${VAR#prefix} | Remove shortest prefix match |
| ${VAR##prefix} | Remove longest prefix match |
| ${VAR%suffix} | Remove shortest suffix match |
| ${VAR%%suffix} | Remove longest suffix match |
Arrays
| Syntax | What it does |
| arr=(a b c d) | Declare indexed array |
| declare -a arr | Explicitly declare indexed array |
| arr[0]="first" | Assign element at index 0 |
| ${arr[2]} | Access element at index 2 |
| ${arr[@]} | All elements (as separate words) |
| ${#arr[@]} | Number of elements |
| arr+=("new") | Append element to array |
| unset arr[2] | Remove element at index 2 |
| declare -A map | Declare associative array (hash map) |
| map[key]="value" | Set associative array element |
| ${map[key]} | Get value for key |
| ${!map[@]} | All keys of associative array |
Conditionals and Test Operators
| Operator | Tests for |
| -f file | File exists and is a regular file |
| -d dir | Directory exists |
| -e path | Path exists (any type) |
| -r file | File exists and is readable |
| -w file | File exists and is writable |
| -x file | File exists and is executable |
| -z "$str" | String is empty (zero length) |
| -n "$str" | String is non-empty |
| "$a" = "$b" | Strings are equal (use = not == inside [ ]) |
| "$a" != "$b" | Strings are not equal |
| $a -eq $b | Integers are equal |
| $a -ne $b | Integers not equal |
| $a -lt $b | Integer less than |
| $a -le $b | Integer less than or equal |
| $a -gt $b | Integer greater than |
| $a -ge $b | Integer greater than or equal |
| ! condition | Negate condition |
| -s file | File exists and has size > 0 |
| file1 -nt file2 | file1 is newer than file2 |
| file1 -ot file2 | file1 is older than file2 |
Loops and Control Flow
if / elif / else
if [ -f "$FILE" ]; then
echo "File exists"
elif [ -d "$FILE" ]; then
echo "It is a directory"
else
echo "Not found"
fi
# Double brackets allow &&, ||, regex
if [[ "$str" =~ ^[0-9]+$ ]]; then
echo "All digits"
fi
for loops
# for-in list
for fruit in apple banana cherry; do
echo "$fruit"
done
# for-in array
for item in "${arr[@]}"; do
echo "$item"
done
# C-style
for (( i=0; i<10; i++ )); do
echo "$i"
done
# with glob
for f in /etc/*.conf; do
echo "$f"
done
while / until / functions / trap
# while loop
count=0
while [ $count -lt 5 ]; do
echo "count: $count"
(( count++ ))
done
# read lines from file
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# until loop (opposite of while)
until ping -c1 server &>/dev/null; do
echo "Waiting for server..."
sleep 5
done
# function with local vars and return
greet() {
local name="$1"
echo "Hello, $name"
return 0
}
greet "Alice"
echo "Exit: $?"
# here-doc
cat <
Package Management
apt — High-Level Package Manager (Debian/Ubuntu)
| Command | What it does |
| apt update | Refresh package lists from repositories |
| apt upgrade | Upgrade all upgradable packages |
| apt full-upgrade | Upgrade + handle changed dependencies (may remove packages) |
| apt install nginx | Install nginx |
| apt install nginx=1.24.0* | Install specific version |
| apt remove nginx | Remove package, keep config files |
| apt purge nginx | Remove package AND config files |
| apt autoremove | Remove automatically-installed packages no longer needed |
| apt search keyword | Search package names and descriptions |
| apt show nginx | Show package details: version, size, description, deps |
| apt list --installed | List all installed packages |
| apt list --upgradable | List packages with available upgrades |
| apt-get clean | Remove downloaded .deb files from cache |
| apt-get autoclean | Remove only obsolete cached packages |
apt-cache — Query the Package Cache
| Command | What it does |
| apt-cache search nginx | Search package names and descriptions |
| apt-cache show nginx | Show package metadata |
| apt-cache depends nginx | Show what nginx depends on |
| apt-cache rdepends nginx | Show packages that depend on nginx (reverse deps) |
| apt-cache policy nginx | Show installed version, candidate, and pin priority |
dpkg — Low-Level Package Tool
| Command | What it does |
| dpkg -i package.deb | Install a .deb file |
| dpkg -r nginx | Remove package (keep config) |
| dpkg -P nginx | Purge package (remove config too) |
| dpkg -l | List all installed packages |
| dpkg -l 'nginx*' | List packages matching pattern |
| dpkg -L nginx | List all files installed by nginx |
| dpkg -S /usr/bin/ls | Which package owns this file |
| dpkg --get-selections | Export list of all package selections |
| dpkg --set-selections < pkglist | Import package selections |
| dpkg-query -W -f='${Status}' nginx | Check installation status of nginx |
Held Packages and PPAs
| Command | What it does |
| apt-mark hold nginx | Pin nginx to current version (prevents upgrade) |
| apt-mark unhold nginx | Remove hold on nginx |
| apt-mark showhold | List all held packages |
| add-apt-repository ppa:user/repo | Add a PPA (Personal Package Archive) |
| add-apt-repository --remove ppa:user/repo | Remove a PPA |
snap — Containerized Packages
| Command | What it does |
| snap install code --classic | Install VS Code snap (classic=unrestricted) |
| snap remove code | Remove a snap |
| snap list | List installed snaps with version and tracking channel |
| snap find keyword | Search Snap Store |
| snap info code | Show details, versions, channels |
| snap refresh | Update all snaps |
| snap refresh code | Update a specific snap |
| snap revert code | Revert snap to previous version |
/etc/apt/sources.list Structure
/etc/apt/sources.list format
# Format: deb [options] uri suite components
deb http://archive.ubuntu.com/ubuntu noble main restricted universe multiverse
deb http://archive.ubuntu.com/ubuntu noble-updates main restricted universe multiverse
deb http://security.ubuntu.com/ubuntu noble-security main restricted universe multiverse
# Signed-by with modern key format
deb [signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable
# Additional sources go in /etc/apt/sources.list.d/
# e.g. /etc/apt/sources.list.d/docker.list
Components: main=officially supported FOSS, restricted=non-free drivers, universe=community FOSS, multiverse=non-free software
SSH & Remote Access
Basic SSH Usage
| Command | What it does |
| ssh user@host | Connect to host as user |
| ssh -p 2222 user@host | Connect on non-standard port 2222 |
| ssh -i ~/.ssh/mykey user@host | Use specific private key file |
| ssh -v user@host | Verbose: debug connection issues (-vvv for more) |
| ssh -X user@host | Enable X11 forwarding (run GUI apps) |
| ssh -A user@host | Forward SSH agent (use local keys on remote) |
| ssh user@host 'command' | Run single command on remote, then exit |
| ssh -t user@host 'sudo bash' | Force pseudo-TTY allocation (for interactive remote commands) |
scp — Secure Copy
| Command | What it does |
| scp file.txt user@host:/remote/path/ | Copy local file to remote |
| scp user@host:/remote/file.txt ./ | Copy remote file to local directory |
| scp -r dir/ user@host:/remote/ | Recursively copy directory |
| scp -P 2222 file user@host:/path/ | Use non-standard port (uppercase -P) |
| scp -i ~/.ssh/key file user@host:/path/ | Use specific identity file |
| scp user1@host1:/file user2@host2:/dir/ | Copy between two remote hosts |
rsync — Efficient Sync & Transfer
| Command | What it does |
| rsync -avz src/ user@host:/dst/ | Sync dir: archive mode, verbose, gzip compress |
| rsync -avz --progress src/ dst/ | Show per-file progress |
| rsync -avz --delete src/ dst/ | Delete files at destination not in source |
| rsync -avz -e "ssh -p 2222" src/ user@host:/dst/ | Use SSH on custom port |
| rsync -avz --exclude="*.log" src/ dst/ | Exclude log files |
| rsync -avz --exclude-from=exclude.txt src/ dst/ | Exclude patterns from file |
| rsync -n -avz src/ dst/ | Dry run (show what would be done, no changes) |
| rsync -avz --checksum src/ dst/ | Compare by checksum not timestamp/size |
rsync flag meanings: a=archive (rlptgoD), r=recursive, l=symlinks, p=permissions, t=timestamps, g=group, o=owner, D=device files, v=verbose, z=compress
SSH Key Generation
| Command | What it does |
| ssh-keygen -t ed25519 -C "comment" | Generate Ed25519 key (recommended, modern) |
| ssh-keygen -t rsa -b 4096 -C "comment" | Generate RSA 4096-bit key (wider compatibility) |
| ssh-keygen -f ~/.ssh/mykey | Specify key file name/path |
| ssh-keygen -p -f ~/.ssh/mykey | Change passphrase on existing key |
| ssh-keygen -y -f ~/.ssh/mykey | Show public key from private key file |
| ssh-copy-id user@host | Copy default public key to remote authorized_keys |
| ssh-copy-id -i ~/.ssh/mykey.pub user@host | Copy specific public key |
| ssh-copy-id -p 2222 user@host | Copy to host on non-standard port |
Manual authorized_keys setup
# On the remote server:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
cat >> ~/.ssh/authorized_keys << 'EOF'
ssh-ed25519 AAAA...your-public-key... comment
EOF
chmod 600 ~/.ssh/authorized_keys
~/.ssh/config — Client Config File
~/.ssh/config example
Host myserver
HostName 192.168.1.100
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519
ForwardAgent yes
Host prod
HostName prod.example.com
User deploy
IdentityFile ~/.ssh/prod_key
StrictHostKeyChecking yes
Host bastion
HostName bastion.example.com
User ec2-user
IdentityFile ~/.ssh/aws.pem
# Jump through bastion to reach internal host
Host internal
HostName 10.0.1.50
User admin
ProxyJump bastion
# Wildcard for all hosts
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
SSH Agent
| Command | What it does |
| eval $(ssh-agent -s) | Start SSH agent and set environment variables |
| ssh-add ~/.ssh/id_ed25519 | Add private key to agent |
| ssh-add -l | List keys currently loaded in agent |
| ssh-add -d ~/.ssh/id_ed25519 | Remove specific key from agent |
| ssh-add -D | Remove all keys from agent |
| ssh-add -t 3600 ~/.ssh/key | Add key with 1-hour expiry |
Port Forwarding and sshfs
| Command | What it does |
| ssh -L 8080:localhost:80 user@host | Local forward: access host's port 80 at localhost:8080 |
| ssh -L 5432:db-server:5432 user@jump | Local forward through jump host to db-server:5432 |
| ssh -R 8080:localhost:3000 user@host | Remote forward: expose local port 3000 as host:8080 |
| ssh -D 1080 user@host | Dynamic: SOCKS5 proxy on localhost:1080 |
| ssh -fN -L 8080:localhost:80 user@host | Background (-f) no-command (-N) tunnel |
| sshfs user@host:/remote/path ~/mountpoint | Mount remote directory over SSH |
| fusermount -u ~/mountpoint | Unmount sshfs mount |
Cron Jobs & Scheduling
crontab — Managing Cron Jobs
| Command | What it does |
| crontab -e | Edit current user's crontab (opens in $EDITOR) |
| crontab -l | List current user's cron jobs |
| crontab -r | Remove all cron jobs for current user (no confirmation!) |
| crontab -u alice -e | Edit alice's crontab (root only) |
| crontab -u alice -l | List alice's crontab |
Cron Syntax
Cron field format
# ┌───────── minute (0 - 59)
# │ ┌─────── hour (0 - 23)
# │ │ ┌───── day of month (1 - 31)
# │ │ │ ┌─── month (1 - 12 or JAN-DEC)
# │ │ │ │ ┌─ day of week (0 - 7, 0 and 7 = Sunday, or SUN-SAT)
# │ │ │ │ │
# * * * * * command to execute
# Special values:
# * = every (any value)
# , = list: 1,15,30
# - = range: 1-5
# / = step: */5 (every 5)
Cron Examples
| Schedule | When it runs |
| */5 * * * * | Every 5 minutes |
| 0 * * * * | Every hour at :00 |
| 0 3 * * * | Every day at 3:00 AM |
| 30 8 * * 1-5 | Weekdays (Mon-Fri) at 8:30 AM |
| 0 0 * * 0 | Every Sunday at midnight |
| 0 2 1 * * | First of every month at 2:00 AM |
| 0 0 1 1 * | Every January 1st at midnight |
| */15 9-17 * * 1-5 | Every 15 min during business hours Mon-Fri |
| 0 4 * * 1 | Every Monday at 4:00 AM |
| 5 0 * 8 * | 5 minutes after midnight in August |
| 0,30 * * * * | Every 30 minutes (at :00 and :30) |
Special Cron Strings
| String | Equivalent to |
| @reboot | Run once at startup |
| @yearly / @annually | 0 0 1 1 * |
| @monthly | 0 0 1 * * |
| @weekly | 0 0 * * 0 |
| @daily / @midnight | 0 0 * * * |
| @hourly | 0 * * * * |
Cron Environment and Logging
Crontab with environment and logging
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com
# Send all cron mail to root (empty string suppresses mail)
# MAILTO=""
# Run backup and log output
0 2 * * * /home/alice/backup.sh >> /var/log/backup.log 2>&1
# Run with full path (cron has minimal PATH)
@reboot /usr/bin/python3 /home/alice/startup.py >> /var/log/startup.log 2>&1
Cron has a minimal PATH. Always use full paths to commands or set PATH at top of crontab. Redirect stderr too (2>&1) to capture errors.
System Cron Directories
| Path | Purpose |
| /etc/crontab | System crontab (has extra username field) |
| /etc/cron.d/ | Package-provided crontab files (same format as /etc/crontab) |
| /etc/cron.daily/ | Scripts run daily by run-parts |
| /etc/cron.weekly/ | Scripts run weekly |
| /etc/cron.monthly/ | Scripts run monthly |
| /etc/cron.hourly/ | Scripts run hourly |
| /var/spool/cron/crontabs/ | Per-user crontab files (managed by crontab command) |
at — One-Time Scheduling
| Command | What it does |
| at now + 1 hour | Schedule job 1 hour from now (type commands, end with Ctrl+D) |
| at 14:30 | Schedule for today at 2:30 PM |
| at 9am tomorrow | Schedule for 9 AM tomorrow |
| at midnight next friday | Schedule for midnight Friday |
| echo "command" | at now + 5 min | Pipe command to at |
| atq | List pending at jobs |
| atrm 3 | Remove job number 3 from queue |
systemd Timers
| Command | What it does |
| systemctl list-timers | List all active timers with next trigger time |
| systemctl list-timers --all | List all timers including inactive ones |
| systemctl enable --now mytimer.timer | Enable and start a timer |
systemd timer example: /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00 # Daily at 2 AM
# OnBootSec=5min # 5 minutes after boot (monotonic)
# OnUnitActiveSec=1h # Every hour after last activation
Persistent=true # Run missed jobs after downtime
Unit=backup.service
[Install]
WantedBy=timers.target
OnCalendar syntax: daily, weekly, Mon *-*-* 04:00:00, *-*-* 00/6:00:00 (every 6 hours)
Files, Archives & Search
find — Search for Files
| Command | What it does |
| find . -name "*.txt" | Find files named *.txt in current directory tree |
| find / -name "file.conf" 2>/dev/null | Find system-wide, suppress permission errors |
| find . -iname "*.PNG" | Case-insensitive name match |
| find . -type f | Regular files only |
| find . -type d | Directories only |
| find . -type l | Symbolic links only |
| find . -maxdepth 2 | Search at most 2 levels deep |
| find . -mtime -7 | Modified in last 7 days (use +7 for older than 7) |
| find . -mmin -60 | Modified in last 60 minutes |
| find . -newer ref.txt | Files newer than ref.txt |
| find . -size +10M | Files larger than 10 MB (k=KB, M=MB, G=GB) |
| find . -size -100k | Files smaller than 100 KB |
| find . -perm 644 | Files with exactly 644 permissions |
| find . -perm /u+x | Files with owner execute bit set |
| find . -user alice | Files owned by alice |
| find . -empty | Empty files or directories |
| find . -name "*.log" -exec rm {} \; | Delete each found file (slower, one rm per file) |
| find . -name "*.log" -exec rm {} + | Delete found files (faster, batches args) |
| find . -name "*.log" -delete | Delete found files (most efficient) |
| find . -name "*.txt" -print0 | xargs -0 grep "term" | Grep through found files (handles spaces in names) |
Links — Hard and Soft
| Command | What it does |
| ln file hardlink | Create hard link (points to same inode; both must be on same filesystem) |
| ln -s /path/to/target symlink | Create symbolic link (can cross filesystems, can link dirs) |
| ls -li | Show inode numbers (hard links share same inode) |
| readlink symlink | Show target of symlink |
| readlink -f symlink | Show fully resolved absolute path |
| find . -type l | Find all symbolic links |
| find . -xtype l | Find broken symbolic links |
Hard link: deleting one doesn't remove data (data survives until all links gone). Symlink: deleting target breaks the link. Directories cannot have hard links (with rare exceptions).
tar — Archiving
| Command | What it does |
| tar -czf archive.tar.gz dir/ | Create gzip-compressed archive |
| tar -cjf archive.tar.bz2 dir/ | Create bzip2-compressed archive |
| tar -cJf archive.tar.xz dir/ | Create xz-compressed archive (best compression) |
| tar -cf archive.tar dir/ | Create uncompressed tar archive |
| tar -xzf archive.tar.gz | Extract gzip archive to current directory |
| tar -xzf archive.tar.gz -C /target/ | Extract to specific directory |
| tar -tf archive.tar.gz | List contents without extracting |
| tar -xzf archive.tar.gz file.txt | Extract only specific file |
| tar -czf archive.tar.gz --exclude="*.log" dir/ | Create archive excluding .log files |
| tar -czf - dir/ | ssh user@host 'tar -xzf - -C /dst/' | Stream archive directly to remote host |
Common flags: c=create, x=extract, t=list, f=file, z=gzip, j=bzip2, J=xz, v=verbose, C=change to directory
Compression Tools
| Command | What it does |
| gzip file.txt | Compress file.txt to file.txt.gz (removes original) |
| gzip -k file.txt | Compress keeping original |
| gunzip file.txt.gz | Decompress (same as gzip -d) |
| gzip -l file.gz | Show compression ratio |
| bzip2 file.txt | Compress with bzip2 (better ratio, slower than gzip) |
| bunzip2 file.txt.bz2 | Decompress bzip2 |
| xz -k file.txt | Compress with xz (best ratio, slowest) |
| unxz file.txt.xz | Decompress xz |
| zip archive.zip file1 file2 | Create zip archive |
| zip -r archive.zip dir/ | Zip directory recursively |
| unzip archive.zip | Extract zip archive |
| unzip -l archive.zip | List zip contents without extracting |
| unzip archive.zip -d /target/ | Extract to specific directory |
File Information and Manipulation
| Command | What it does |
| file document.pdf | Detect file type by magic bytes (not extension) |
| file * | Detect type of all files in current directory |
| stat file.txt | Show inode info: size, permissions, timestamps, inode number |
| dd if=/dev/urandom of=test.img bs=1M count=100 | Create 100 MB file of random data |
| dd if=/dev/zero of=zero.img bs=4M count=250 | Create 1 GB file of zeros |
| dd if=/dev/sda of=backup.img bs=4M | Disk image backup (raw copy of sda) |
| truncate -s 100M file.img | Create sparse 100 MB file (or resize existing) |
| fallocate -l 1G bigfile | Pre-allocate 1 GB file instantly (not sparse) |
| shred -vzu -n 3 sensitive.txt | Overwrite 3 times, then zero, then delete (-v verbose, -z zero pass, -u delete) |
| locate filename | Fast filename search using database (may be stale) |
| updatedb | Update locate database (usually run by cron daily) |
Pipes, Redirection & Shell Features
Standard Streams and Redirection
Every process has three default streams: stdin (0), stdout (1), stderr (2).
| Operator | What it does |
| > file | Redirect stdout to file (overwrite) |
| >> file | Redirect stdout to file (append) |
| 2> file | Redirect stderr to file (overwrite) |
| 2>> file | Redirect stderr to file (append) |
| 2>&1 | Redirect stderr to wherever stdout goes |
| &> file | Redirect both stdout and stderr to file (bash shorthand) |
| &>> file | Append both stdout and stderr to file |
| > /dev/null | Discard stdout |
| > /dev/null 2>&1 | Discard all output (stdout and stderr) |
| < file | Redirect file as stdin to command |
| cmd1 | cmd2 | Pipe stdout of cmd1 to stdin of cmd2 |
| cmd1 |& cmd2 | Pipe stdout and stderr of cmd1 to cmd2 (bash) |
Common redirection patterns
# Capture stdout and stderr to file
command > output.log 2>&1
# stderr to separate file
command > out.log 2> err.log
# Order matters: redirect stderr to where stdout goes
command 2>&1 | grep "error" # correct
# command | grep "error" 2>&1 # WRONG: 2>&1 affects grep, not command
# Discard stderr, keep stdout
command 2>/dev/null
tee — Pipe and Save
| Command | What it does |
| cmd | tee file.txt | Write stdout to file AND print to terminal |
| cmd | tee -a file.txt | Append to file while also printing |
| cmd | tee file1 file2 | Write to multiple files simultaneously |
| cmd 2>&1 | tee log.txt | Capture stdout+stderr to file and terminal |
Process Substitution and Command Substitution
| Syntax | What it does |
| $(command) | Command substitution: replace with output of command |
| `command` | Command substitution (older backtick form, avoid nesting) |
| <(command) | Process substitution: treat command output as a file (for reading) |
| >(command) | Process substitution: treat command as a file (for writing) |
| diff <(sort a.txt) <(sort b.txt) | Diff sorted versions without creating temp files |
| tee >(gzip > out.gz) | wc -l | Compress and count lines simultaneously |
Here-doc, Here-string, and xargs
Here-doc and here-string
# Here-doc: multi-line string as stdin
cat <
| Command | What it does |
| cmd | xargs command | Pass stdin lines as arguments to command |
| cmd | xargs -I{} cp {} /backup/ | Replace {} with each input line |
| cmd | xargs -n 2 | Pass 2 arguments per invocation |
| cmd | xargs -P 4 | Run 4 processes in parallel |
| find . -print0 | xargs -0 rm | Null-separated (-0) safe for filenames with spaces |
| xargs -a file.txt command | Read arguments from file instead of stdin |
Subshells, Grouping, and Named Pipes
| Syntax | What it does |
| ( cmd1; cmd2 ) | Subshell: runs in child process; variable changes don't affect parent |
| { cmd1; cmd2; } | Command group: runs in current shell; note trailing semicolon and spaces |
| ( cd /tmp; ls ) | cd only affects the subshell, not your current directory |
| mkfifo /tmp/mypipe | Create a named pipe (FIFO) |
| cmd1 > /tmp/mypipe & | Write to named pipe in background |
| cmd2 < /tmp/mypipe | Read from named pipe |
Brace Expansion
| Syntax | Expands to |
| echo {a,b,c} | a b c |
| echo file{1,2,3}.txt | file1.txt file2.txt file3.txt |
| echo {1..5} | 1 2 3 4 5 |
| echo {01..05} | 01 02 03 04 05 (zero-padded) |
| echo {a..z} | a b c ... z |
| echo {1..10..2} | 1 3 5 7 9 (step of 2) |
| mkdir -p project/{src,tests,docs} | Create 3 subdirectories at once |
| cp file.txt{,.bak} | Copy file.txt to file.txt.bak |
| mv config{.old,} | Rename config.old to config |
Vim Editor Reference
Vim Modes
| Mode | How to enter / what it is |
| Normal mode | Default mode. Press Esc from any mode. For navigation and operations. |
| Insert mode | Press i to insert before cursor. Status shows -- INSERT --. |
| Visual mode | Press v (char), V (line), or Ctrl+v (block). For selection. |
| Command mode | Press : from Normal. For file operations and settings. |
| Replace mode | Press R from Normal. Overwrites existing characters. |
Motion Keys
| Key | Movement |
| h j k l | Left, down, up, right |
| w | Next word start |
| b | Previous word start |
| e | End of current/next word |
| W B E | Same as w/b/e but WORD (space-delimited, ignores punctuation) |
| 0 | Start of line (column 0) |
| ^ | First non-whitespace character of line |
| $ | End of line |
| gg | First line of file |
| G | Last line of file |
| 50G or :50 | Go to line 50 |
| { } | Previous / next empty line (paragraph movement) |
| % | Jump to matching bracket/paren/brace |
| Ctrl+d | Scroll down half page |
| Ctrl+u | Scroll up half page |
| Ctrl+f | Scroll forward (down) full page |
| Ctrl+b | Scroll backward (up) full page |
| zz | Center current line in window |
| H M L | Move to top / middle / bottom of screen |
Editing in Normal Mode
| Key | Action |
| i | Insert before cursor |
| I | Insert at start of line |
| a | Append after cursor |
| A | Append at end of line |
| o | Open new line below and enter Insert mode |
| O | Open new line above and enter Insert mode |
| x | Delete character under cursor |
| X | Delete character before cursor (backspace) |
| dd | Delete (cut) current line |
| 3dd | Delete 3 lines |
| dw | Delete from cursor to end of word |
| d$ | Delete from cursor to end of line |
| d0 | Delete from start of line to cursor |
| D | Delete from cursor to end of line (same as d$) |
| yy | Yank (copy) current line |
| yw | Yank word |
| y$ | Yank to end of line |
| p | Paste after cursor / below current line |
| P | Paste before cursor / above current line |
| u | Undo last change |
| Ctrl+r | Redo (undo the undo) |
| . | Repeat last change (very powerful) |
| ~ | Toggle case of character under cursor |
| J | Join current line with next |
| cw | Change word (delete then enter Insert mode) |
| cc | Change entire line |
| C | Change to end of line |
| r | Replace single character (stays in Normal mode) |
Search and Replace
| Command | What it does |
| /pattern | Search forward for pattern |
| ?pattern | Search backward for pattern |
| n | Next match (same direction) |
| N | Previous match (reverse direction) |
| * | Search forward for word under cursor |
| # | Search backward for word under cursor |
| :%s/old/new/g | Replace all occurrences in file |
| :%s/old/new/gc | Replace all with confirmation for each |
| :%s/old/new/gi | Replace all case-insensitively |
| :5,10s/old/new/g | Replace only in lines 5-10 |
| :s/old/new/ | Replace first occurrence on current line |
| :noh | Clear search highlighting |
Command Mode & Windows
| Command | What it does |
| :w | Save file |
| :w filename | Save as filename |
| :q | Quit (fails if unsaved changes) |
| :q! | Quit discarding changes |
| :wq or :x | Save and quit |
| ZZ | Save and quit (Normal mode shortcut) |
| :e filename | Open file in current buffer |
| :split / :sp | Horizontal split |
| :vsplit / :vsp | Vertical split |
| Ctrl+w w | Switch between split windows |
| :tabnew | Open new tab |
| gt / gT | Next / previous tab |
| :set number | Show line numbers |
| :set paste | Paste mode (disables auto-indent when pasting) |
| :set nopaste | Disable paste mode |
Marks and Macros
| Key | Action |
| ma | Set mark 'a' at current position (a-z: file-local, A-Z: global) |
| 'a | Jump to line of mark 'a' |
| `a | Jump to exact position (line and column) of mark 'a' |
| :marks | List all marks |
| qa | Start recording macro into register 'a' |
| q | Stop recording macro |
| @a | Play macro from register 'a' |
| 10@a | Play macro 10 times |
| @@ | Repeat last played macro |
Common .vimrc Settings
~/.vimrc essentials
set number " Show line numbers
set relativenumber " Relative line numbers
set tabstop=4 " Tab = 4 spaces wide
set shiftwidth=4 " Indent by 4 spaces
set expandtab " Use spaces instead of tabs
set smartindent " Auto-indent new lines
set hlsearch " Highlight search results
set incsearch " Incremental search as you type
set ignorecase " Case-insensitive search...
set smartcase " ...unless search has uppercase
set wrap " Wrap long lines
set linebreak " Wrap at word boundaries
set scrolloff=5 " Keep 5 lines above/below cursor
set wildmenu " Command completion menu
set clipboard=unnamed " Use system clipboard
set backspace=indent,eol,start " Sane backspace
syntax on " Syntax highlighting
colorscheme desert " Color scheme
Environment & Shell Config
Viewing and Setting Environment Variables
| Command | What it does |
| printenv | Print all environment variables |
| printenv HOME | Print value of HOME variable |
| env | Print environment or run command with modified environment |
| env VAR=val command | Run command with VAR set, without modifying current shell |
| set | Print all shell variables, functions, and env vars (verbose) |
| VAR=value | Set shell variable (not exported — child processes don't see it) |
| export VAR=value | Set and export to environment (child processes inherit it) |
| export VAR | Export existing shell variable to environment |
| unset VAR | Delete variable from shell and environment |
| declare -x VAR=val | Declare and export (same as export VAR=val) |
Common Environment Variables
| Variable | Meaning |
| $HOME | Current user's home directory (/home/alice) |
| $USER | Current username |
| $SHELL | Path to current shell (/bin/bash) |
| $PWD | Current working directory (same as pwd command) |
| $OLDPWD | Previous working directory (cd - uses this) |
| $PATH | Colon-separated list of directories searched for commands |
| $EDITOR | Default text editor (used by git commit, crontab -e, etc.) |
| $VISUAL | Visual editor (preferred over $EDITOR for interactive use) |
| $PAGER | Default pager for viewing long output (e.g. less) |
| $TERM | Terminal type (e.g. xterm-256color) |
| $LANG | Locale (e.g. en_US.UTF-8) |
| $PS1 | Primary prompt string |
| $PS2 | Secondary prompt (shown when command continues) |
| $HISTSIZE | Number of commands kept in history in memory |
| $HISTFILESIZE | Number of commands kept in ~/.bash_history file |
| $TMPDIR | Temporary directory (defaults to /tmp if unset) |
| $UID | Current user's UID (0 = root) |
PATH — How It Works
Working with PATH
# View current PATH
echo $PATH
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Add directory to PATH (prepend = higher priority)
export PATH="/opt/myapp/bin:$PATH"
# Add to end of PATH (lower priority)
export PATH="$PATH:/opt/legacy/bin"
# Make permanent: add to ~/.bashrc or ~/.profile
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
# Find which binary will be executed
which python3
type python3 # shows alias/function/builtin/file
command -v git # portable alternative to which
.bashrc vs .bash_profile vs .profile
| File | When it runs |
| ~/.bashrc | Interactive non-login shells (new terminal tab, bash in X). Aliases, functions, PS1, per-session settings. |
| ~/.bash_profile | Login shells (SSH login, console login). Usually sources ~/.bashrc. Good for export statements. |
| ~/.profile | Login shells for any POSIX shell. Used when .bash_profile doesn't exist. sh-compatible only. |
| ~/.bash_logout | Run when a login bash shell exits. |
| /etc/profile | System-wide login shell config (runs for all users). |
| /etc/bash.bashrc | System-wide .bashrc (Debian/Ubuntu). |
| /etc/profile.d/*.sh | Modular scripts sourced by /etc/profile. |
Best practice: put exports and PATH changes in ~/.bash_profile (or ~/.profile). Put aliases, functions, and PS1 in ~/.bashrc. Have .bash_profile source ~/.bashrc.
Aliases and Functions
| Command | What it does |
| alias ll='ls -lah' | Create alias (current session only) |
| alias | List all current aliases |
| unalias ll | Remove alias |
| type ll | Show what 'll' resolves to (alias, function, etc.) |
| \ll | Bypass alias and run original command |
~/.bashrc: aliases and functions
# Aliases
alias ll='ls -lah --color=auto'
alias la='ls -A'
alias grep='grep --color=auto'
alias ..='cd ..'
alias ...='cd ../..'
alias df='df -h'
alias du='du -sh'
alias ports='ss -tulpn'
# Function: make directory and cd into it
mkcd() {
mkdir -p "$1" && cd "$1"
}
# Function: 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" ;;
*) echo "Unknown format: $1" ;;
esac
}
PS1 Prompt Customization
PS1 escape codes and examples
# Common PS1 escape sequences:
# \u = username \h = hostname (short) \H = hostname (full)
# \w = full CWD \W = basename of CWD \$ = # if root else $
# \t = time HH:MM:SS \d = date
# \n = newline
# ANSI color codes: \[\033[COLORm\] reset: \[\033[0m\]
# Colors: 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
# Green user@host, blue directory, reset
PS1='\[\033[32m\]\u@\h\[\033[0m\]:\[\033[34m\]\w\[\033[0m\]\$ '
# Show last exit status (red if error)
PS1='$(if [ $? -eq 0 ]; then echo "\[\033[32m\]OK"; else echo "\[\033[31m\]ERR"; fi)\[\033[0m\] \w \$ '
# With git branch (requires __git_ps1 from git-prompt.sh)
PS1='\[\033[32m\]\u@\h\[\033[0m\]:\[\033[34m\]\w\[\033[33m\]$(__git_ps1 " (%s)")\[\033[0m\]\$ '
source, dot, and exec
| Command | What it does |
| source ~/.bashrc | Execute script in current shell (variable changes take effect) |
| . ~/.bashrc | Same as source (POSIX dot command) |
| ./script.sh | Run script in a subshell (changes don't affect current shell) |
| bash script.sh | Run script in a new bash subshell |
| exec bash | Replace current shell with new bash (use to reload shell) |
User & Group Management
useradd and adduser
useradd is the low-level binary (same on all distros). adduser is a higher-level Debian/Ubuntu script that prompts interactively and creates home dir by default.
| Command | What it does |
| useradd alice | Create user alice (no home dir, no password by default) |
| useradd -m alice | Create user with home directory (/home/alice) |
| useradd -m -s /bin/bash alice | With home dir and bash shell |
| useradd -m -G sudo,docker alice | Add to supplementary groups |
| useradd -u 1500 alice | Specify UID |
| useradd -d /custom/home alice | Set custom home directory |
| adduser alice | Interactive: create user with home dir, set password (Debian/Ubuntu) |
| adduser alice sudo | Add existing user alice to sudo group |
passwd, usermod, userdel
| Command | What it does |
| passwd alice | Set or change alice's password (root) |
| passwd | Change your own password |
| passwd -l alice | Lock account (prefix ! to password hash in /etc/shadow) |
| passwd -u alice | Unlock account |
| passwd -e alice | Expire password (force change on next login) |
| usermod -aG docker alice | Add alice to docker group (-a means append, not replace) |
| usermod -G sudo,docker alice | Set supplementary groups (replaces existing groups — use -aG to append) |
| usermod -L alice | Lock account (same as passwd -l) |
| usermod -U alice | Unlock account |
| usermod -s /bin/zsh alice | Change login shell |
| usermod -d /new/home -m alice | Change home directory and move files (-m) |
| usermod -l newname alice | Rename user account |
| userdel alice | Delete user (keep home directory) |
| userdel -r alice | Delete user and home directory and mail spool |
User Info Commands
| Command | What it does |
| id | Show current user's UID, GID, and all groups |
| id alice | Show alice's UID, GID, and groups |
| whoami | Print current effective username |
| groups | List groups current user belongs to |
| groups alice | List alice's groups |
| who | Who is logged in right now |
| w | Who is logged in with what they're running |
| last | History of logins from /var/log/wtmp |
| last alice | Login history for alice |
| lastlog | Most recent login of every user |
| lastb | Failed login attempts from /var/log/btmp |
Group Management
| Command | What it does |
| groupadd devs | Create new group devs |
| groupadd -g 1500 devs | Create group with specific GID |
| groupdel devs | Delete group devs |
| groupmod -n newname devs | Rename group |
| newgrp docker | Switch primary group to docker for current session (reloads shell) |
| getent group docker | Show docker group entry (members, GID) |
/etc/passwd and /etc/shadow
/etc/passwd format (world-readable)
# username:password:UID:GID:GECOS:home:shell
alice:x:1001:1001:Alice Smith,,,:/home/alice:/bin/bash
# x = password is in /etc/shadow
# UID 0 = root, 1-999 = system accounts, 1000+ = regular users
/etc/shadow format (root-readable only)
# username:hash:lastchange:minage:maxage:warn:inactive:expire:reserved
alice:$6$salt$hashhere...:19600:0:99999:7:::
# $6$ = SHA-512, $5$ = SHA-256, $1$ = MD5 (legacy)
# ! or !! at start = locked account
# lastchange = days since epoch (Jan 1 1970)
sudo and su
| Command | What it does |
| sudo command | Run command as root |
| sudo -u alice command | Run command as alice |
| sudo -i | Open root login shell (full environment, like logging in as root) |
| sudo -s | Open root shell keeping current environment |
| sudo -l | List what sudo commands current user can run |
| sudo -l -U alice | List alice's sudo privileges (as root) |
| visudo | Edit /etc/sudoers safely (validates syntax before saving) |
| su alice | Switch to alice (keeps current env; requires alice's password) |
| su - alice | Switch to alice with full login shell (loads alice's env) |
| su - | Switch to root login shell |
/etc/sudoers rule syntax
# Format: WHO WHERE=(AS_WHO:AS_GROUP) WHAT
alice ALL=(ALL:ALL) ALL # alice can run any command as any user
%devs ALL=(ALL) NOPASSWD: /bin/systemctl restart nginx
# %devs = group devs, NOPASSWD = no password prompt
bob ALL=(ALL) /usr/bin/apt, /usr/bin/apt-get
# bob can only run apt/apt-get
deploy ALL=(root) NOPASSWD: /opt/deploy/deploy.sh
Systemd & Service Management
systemctl — Service Control
| Command | What it does |
| systemctl start nginx | Start service now |
| systemctl stop nginx | Stop service now |
| systemctl restart nginx | Stop and start service (breaks connections) |
| systemctl reload nginx | Reload config without stopping (if supported) |
| systemctl enable nginx | Enable service at boot (creates symlink in wants/) |
| systemctl disable nginx | Disable service at boot |
| systemctl enable --now nginx | Enable AND start immediately |
| systemctl status nginx | Show service status, last log lines, and active state |
| systemctl is-active nginx | Print "active" or "inactive" (good for scripts) |
| systemctl is-enabled nginx | Print "enabled" or "disabled" |
| systemctl list-units --type=service | List all loaded services |
| systemctl list-unit-files --type=service | List all installed services with enabled/disabled status |
| systemctl list-timers | List all active timers with next trigger time |
| systemctl daemon-reload | Reload systemd after adding/changing unit files |
| systemctl mask nginx | Prevent service from being started (links to /dev/null) |
| systemctl unmask nginx | Remove mask |
| systemctl poweroff | Shut down system |
| systemctl reboot | Reboot system |
journalctl — Log Viewer
| Command | What it does |
| journalctl | All logs from oldest to newest |
| journalctl -u nginx | Logs for nginx unit only |
| journalctl -f | Follow (live tail) journal logs |
| journalctl -f -u nginx | Follow nginx logs |
| journalctl -n 50 | Last 50 lines |
| journalctl -n 50 -u nginx | Last 50 lines of nginx |
| journalctl --since "1 hour ago" | Logs from last hour |
| journalctl --since "2026-09-04 08:00" --until "2026-09-04 09:00" | Logs in time range |
| journalctl -p err | Only error-level and above messages |
| journalctl -p warning..err | Warning through error priority range |
| journalctl -b | Logs from current boot only |
| journalctl -b -1 | Logs from previous boot |
| journalctl --list-boots | List all boots with IDs and timestamps |
| journalctl --disk-usage | Show journal disk usage |
| journalctl --vacuum-size=500M | Keep only most recent 500 MB of logs |
| journalctl -o json-pretty -n 5 -u nginx | Output as pretty-printed JSON |
Unit File Anatomy
Unit file sections and common directives
[Unit]
Description=My Web Application # Human-readable name
Documentation=https://example.com # Link to docs
After=network.target postgresql.service # Start after these
Wants=postgresql.service # Soft dependency (start if possible)
Requires=network.target # Hard dependency (fail if missing)
[Service]
Type=simple # simple|forking|oneshot|notify|dbus
ExecStart=/usr/bin/python3 /opt/myapp/app.py
ExecReload=/bin/kill -HUP $MAINPID # Command for reload
ExecStop=/bin/kill -TERM $MAINPID # Graceful stop
Restart=on-failure # always|on-failure|on-abort|no
RestartSec=5 # Wait 5s before restart
User=myapp # Run as this user
Group=myapp
WorkingDirectory=/opt/myapp
Environment="NODE_ENV=production" # Set env var
EnvironmentFile=/etc/myapp.env # Load env from file
StandardOutput=journal # stdout to journal
StandardError=journal # stderr to journal
[Install]
WantedBy=multi-user.target # Target that should want this unit
Service Types
| Type | Use case |
| simple | ExecStart is the main process. Service ready immediately after start. Default. |
| forking | Process forks and parent exits. Use PIDFile= to track child. Traditional daemons. |
| oneshot | Process does a task and exits. Use with RemainAfterExit=yes for status. |
| notify | Like simple, but service sends sd_notify() when ready. Most reliable for ordering. |
| dbus | Service is ready when it takes a D-Bus name. Set BusName=. |
Targets (Runlevels)
| Target | Equivalent to / purpose |
| poweroff.target | Runlevel 0 — shut down |
| rescue.target | Runlevel 1 — single-user/rescue mode |
| multi-user.target | Runlevel 3 — multi-user, no GUI (servers) |
| graphical.target | Runlevel 5 — multi-user with GUI |
| reboot.target | Runlevel 6 — reboot |
| systemctl get-default | Show current default target |
| systemctl set-default multi-user.target | Set server to boot without GUI |
| systemctl isolate rescue.target | Switch to rescue mode now |
systemd-analyze — Boot Performance
| Command | What it does |
| systemd-analyze | Total boot time: firmware + loader + kernel + userspace |
| systemd-analyze blame | List units sorted by initialization time |
| systemd-analyze critical-chain | Show critical path of boot (slowest chain of dependencies) |
| systemd-analyze critical-chain nginx.service | Critical chain to a specific service |
| systemd-analyze plot > boot.svg | Generate SVG boot timeline (open in browser) |
| systemd-analyze verify nginx.service | Check unit file for errors |
Creating a Custom Service
/etc/systemd/system/myapp.service
[Unit]
Description=My Python App
After=network.target
Wants=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/venv/bin/python app.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
Environment="PORT=8080"
Environment="LOG_LEVEL=info"
[Install]
WantedBy=multi-user.target
Deploy and start the service
# After creating the unit file:
systemctl daemon-reload # Reload systemd config
systemctl enable --now myapp # Enable + start
systemctl status myapp # Verify it's running
journalctl -u myapp -f # Follow logs
Drop-in Overrides
| Command | What it does |
| systemctl edit nginx | Open override editor; creates /etc/systemd/system/nginx.service.d/override.conf |
| systemctl edit --full nginx | Edit a full copy of the unit file |
| systemctl revert nginx | Remove all overrides and restore package defaults |
Override example: add memory limit to nginx
# Created by: systemctl edit nginx
# Saved to: /etc/systemd/system/nginx.service.d/override.conf
[Service]
MemoryMax=512M
CPUQuota=50%