Linux 102
layout: cover kicker: Living on the command line · Part two title: Linux 102 subtitle: Processes, tmux, scripting, and remote work
layout: statement kicker: Where 101 left off title: 101 was about getting around. 102 is about staying in control — long-running work, remote boxes, and automating the boring parts.
layout: agenda title: What we'll cover items:
- { topic: Processes & signals, desc: "background, jobs, killing things properly" }
- { topic: tmux, desc: "sessions that survive a dropped connection" }
- { topic: Shell power, desc: "history, expansion, aliases, functions" }
- { topic: Scripting, desc: "from one-liner to a real script" }
- { topic: Networking & remote, desc: "inspect, tunnel, sync" }
- { topic: Services & scheduling, desc: "systemd, logs, cron, archives" }
layout: section index: "01" kicker: Part one title: Processes & signals
layout: default kicker: Foreground vs background title: Stop blocking your terminal
A command normally holds your shell until it finishes. You don't have to wait:
Ctrl+Z suspends whatever's running — then bg resumes it in the background. fg brings it back.
layout: define kicker: Don't lose work to a disconnect term: nohup & disown definition: A background job still dies when the shell closes. nohup (or disown) cuts that cord so it keeps running after you log out. points:
- "nohup ./job.sh & — survives logout, output goes to nohup.out"
- "disown %1 — detach a job you already started"
- "For anything interactive or long, reach for tmux instead (next part)"
layout: reference kicker: Find it, then signal it title: Killing processes properly groups:
- { title: Find, items: [{ term: "ps aux", desc: "every process" }, { term: "pgrep -f name", desc: "PIDs matching a pattern" }, { term: "htop", desc: "interactive, kill with k" }] }
- { title: Signal, items: [{ term: "kill PID", desc: "polite stop (SIGTERM) — lets it clean up" }, { term: "kill -9 PID", desc: "force (SIGKILL) — last resort, no cleanup" }, { term: "kill -HUP PID", desc: "reload config without restarting" }, { term: "pkill -f name", desc: "signal by name, no PID lookup" }] }
layout: statement kicker: The rule title: Always try kill first. Reach for -9 only when it ignores you — it can't clean up after itself.
layout: section index: "02" kicker: Part two title: tmux
layout: define kicker: The remote-work superpower term: tmux definition: A terminal multiplexer — your session lives on the server, not in your SSH connection. Drop wifi, close the laptop, reconnect later: it's all still there. points:
- "Long jobs keep running while you're disconnected"
- "Split one window into many panes and tabs"
- "Reattach from anywhere — same session, same state"
layout: reference kicker: The 20% you'll use daily title: tmux essentials — prefix is Ctrl+B, then a key groups:
- { title: Sessions, items: [{ term: "tmux new -s work", desc: "start a named session" }, { term: "Ctrl+B d", desc: "detach (it keeps running)" }, { term: "tmux ls", desc: "list sessions" }, { term: "tmux a -t work", desc: "reattach" }] }
- { title: Panes & windows, items: [{ term: "Ctrl+B %", desc: "split left/right" }, { term: 'Ctrl+B "', desc: "split top/bottom" }, { term: "Ctrl+B c", desc: "new window (tab)" }, { term: "Ctrl+B arrow", desc: "move between panes" }] }
layout: default kicker: The typical flow title: Start a job, walk away, come back
layout: section index: "03" kicker: Part three title: Shell power
layout: reference kicker: Stop retyping title: History & expansion items:
- { term: "!!", desc: "the last command — sudo !! reruns it as root" }
- { term: "!$", desc: "last argument of the previous command" }
- { term: "Ctrl+R", desc: "fuzzy-search your history" }
- { term: "cd !$", desc: "cd into the dir you just made" }
- { term: "mkdir -p a/{src,test,docs}", desc: "brace expansion — three dirs at once" }
- { term: "cp file{,.bak}", desc: "expands to: cp file file.bak" }
layout: code-explain kicker: Joining commands title: Chaining, substitution, and a better tee notes:
- "&& runs the next only if the last succeeded; || only if it failed."
- "$(...) drops a command's output right into the line."
- "<(...) process substitution — feed a command where a file is expected."
- "Exit code of the last command is in $? — 0 means success."
make && ./run # run only if the build passed
ping -c1 host || echo "down" # fallback on failure
echo "today is $(date +%F)" # inline command output
diff <(sort a.txt) <(sort b.txt) # compare without temp files
layout: default kicker: Make it yours title: Aliases & functions
Stop typing the same things. Put these in your ~/.zshrc (or ~/.bashrc):
alias gs='git status'
alias ll='ls -lah'
# a function when you need arguments
mkcd() { mkdir -p "$1" && cd "$1"; }
After editing, run source ~/.zshrc (or open a new shell) to load the changes.
layout: section index: "04" kicker: Part four title: Scripting
layout: code-explain kicker: Anatomy of a script title: From one-liner to something you trust notes:
- "Shebang picks the interpreter — env finds bash on PATH."
- "set -euo pipefail — stop on error, on unset vars, and on pipe failures. Put it in every script."
- "${1:?msg} requires an argument, with a usage message if missing."
- "Quote your variables — "$f" — or spaces in names will bite you."
#!/usr/bin/env bash
set -euo pipefail
dir=${1:?usage: backup.sh DIR}
for f in "$dir"/*.log; do
echo "archiving $f"
done
layout: reference kicker: Control flow title: Tests, loops, conditionals groups:
- { title: Tests & branches, items: [{ term: "[[ -f path ]]", desc: "file exists?" }, { term: "[[ -z "$x" ]]", desc: "string is empty?" }, { term: "if … then … fi", desc: "branch on a test" }, { term: "case $x in …", desc: "match patterns" }] }
- { title: Loops, items: [{ term: "for f in *.txt", desc: "iterate over files" }, { term: "while read line", desc: "process input line by line" }, { term: "$(seq 1 5)", desc: "1 2 3 4 5" }, { term: "$# $@ $1", desc: "arg count, all args, first arg" }] }
layout: statement kicker: Know when to stop title: Past ~50 lines, or once you need arrays and real data — switch to Python. Bash is glue, not a language to live in.
layout: section index: "05" kicker: Part five title: Networking & remote
layout: reference kicker: What's happening on this box? title: Inspect the network items:
- { term: "ss -tlnp", desc: "what's listening, on which ports" }
- { term: "curl -I https://site", desc: "just the response headers" }
- { term: "dig example.com", desc: "DNS lookup — what does it resolve to" }
- { term: "ping -c3 host", desc: "is it reachable" }
- { term: "nc -zv host 443", desc: "is that specific port open" }
layout: code-explain kicker: SSH is more than a login title: Tunnels — reach things you otherwise can't notes:
- "-L local forward: a port on your machine maps to a host the server can see."
- "Now a remote database looks like it's running on your laptop."
- "-R remote forward: expose your local server to the remote host."
- "Great for hitting a DB behind a bastion, or sharing a local dev server."
# the remote Postgres, as if it were local:
ssh -L 5432:localhost:5432 devbox
# psql -h localhost → hits the server's DB
# expose your local app to the remote box:
ssh -R 8080:localhost:3000 devbox
layout: default kicker: Move files like a pro title: rsync — copy only what changed
rsync -avz src/ user@host:/backup/ # sync a dir to a server (over SSH)
rsync -avz --delete src/ dst/ # mirror exactly — removes extras
rsync -avzP big.iso host:/tmp/ # resumable, with a progress bar
Unlike scp, rsync skips unchanged files and can resume — re-running a big transfer is nearly instant.
layout: section index: "06" kicker: Part six title: Services & scheduling
layout: reference kicker: Long-running services title: systemd & logs groups:
- { title: Manage, items: [{ term: "systemctl status nginx", desc: "is it running? recent logs" }, { term: "systemctl restart nginx", desc: "stop + start" }, { term: "systemctl enable --now x", desc: "start now + on every boot" }] }
- { title: Read the logs, items: [{ term: "journalctl -u nginx -f", desc: "follow a service's logs live" }, { term: "journalctl -u nginx --since '1 hour ago'", desc: "scoped by time" }, { term: "journalctl -p err -b", desc: "errors since last boot" }] }
layout: default kicker: Run it on a schedule title: cron — set it and forget it
crontab -e to edit, crontab -l to list. Five time fields, then the command:
# ┌ min ┌ hour ┌ day ┌ month ┌ weekday
0 3 * * * /home/cagdas/backup.sh # daily 03:00
*/15 * * * * /home/cagdas/health.sh # every 15 min
On systemd boxes, systemd timers are the modern alternative — they log to journalctl and handle missed runs.
layout: reference kicker: The last mile title: Archives, links & disk items:
- { term: "tar -czf out.tar.gz dir/", desc: "create a gzipped archive" }
- { term: "tar -xzf out.tar.gz", desc: "extract it" }
- { term: "ln -s /real/path link", desc: "symlink — a pointer to a file or dir" }
- { term: "du -sh *", desc: "size of each item here" }
- { term: "ncdu", desc: "interactive — find what's eating your disk" }
- { term: "df -h", desc: "free space per mount" }