Linux 101
layout: cover kicker: Living on the command line title: Linux 101 subtitle: SSH, files, pipes, and the modern CLI toolkit
layout: agenda title: What we'll cover items:
- { topic: SSH & keys, desc: "how key auth works, config aliases" }
- { topic: Files & permissions, desc: "moving around, reading, chmod" }
- { topic: Environment, desc: "variables, PATH, getting help" }
- { topic: Pipes & redirection, desc: "connecting small commands" }
- { topic: Text processing, desc: "grep, find, awk, sed, xargs" }
- { topic: Modern CLI tools, desc: "jq, rg, fd, bat, fzf, and friends" }
layout: section index: "01" kicker: Part one title: SSH & keys
layout: define kicker: How it works term: Public & private keys definition: You generate a key pair — two linked files. The public one you hand out; the private one never leaves your machine. points:
- "id_ed25519 — private key (secret, never share)"
- "id_ed25519.pub — public key (give to servers)"
- "The server locks a challenge with your public key; only your private key opens it"
- "Proves who you are — no password ever crosses the network"
layout: default kicker: Setup title: Generate a key and hand it over
layout: default kicker: Quality of life title: SSH config — name your hosts
Define aliases once, stop typing IPs, users, and ports:
Host devbox
HostName 192.168.1.50
User cagdas
Port 2222
IdentityFile ~/.ssh/work_key
Now ssh devbox just works — and so do scp, rsync, and git over that host.
layout: section index: "02" kicker: Part two title: Files & permissions
layout: reference kicker: The fundamentals title: Get around & manage files groups:
- { title: Navigate, items: [{ term: "pwd", desc: "where am I" }, { term: "ls -la", desc: "list all, with details" }, { term: "cd -", desc: "back to previous dir" }, { term: "cd ~", desc: "home" }] }
- { title: Create & move, items: [{ term: "mkdir -p a/b/c", desc: "nested dirs" }, { term: "cp -r src dst", desc: "copy a directory" }, { term: "mv a b", desc: "rename / move" }, { term: "rm -r dir", desc: "delete — no trash, it's gone" }] }
layout: code-explain kicker: Permissions title: Reading an ls -l line notes:
- "First char: - file, d directory."
- "Then three triples: owner, group, others."
- "Each triple is r(ead) w(rite) x(ecute) — x on a dir means 'enter'."
- "Numbers: r=4 w=2 x=1, add per group. 755 = rwx r-x r-x."
-rw-r--r-- cagdas devs config.yaml # file: owner writes, rest read
drwxr-xr-x cagdas devs src/ # dir: owner writes, rest enter
chmod +x script.sh # make executable
chmod 600 id_ed25519 # rw------- (required for SSH keys)
layout: reference kicker: Looking inside title: Read files items:
- { term: "cat f", desc: "dump the whole file" }
- { term: "less f", desc: "scroll (q quits, / searches)" }
- { term: "head -20 / tail -20", desc: "first / last 20 lines" }
- { term: "tail -f app.log", desc: "follow live as lines arrive" }
- { term: "wc -l f", desc: "count lines" }
- { term: "file mystery.bin", desc: "what kind of file is this" }
layout: section index: "03" kicker: Part three title: Environment
layout: default kicker: Variables & PATH title: Where the shell finds things
man ls for the full manual, ls --help for a quick reminder — and tldr ls for practical examples (coming up).
layout: section index: "04" kicker: Part four title: Pipes & redirection
layout: define kicker: The core idea term: The pipe | definition: Sends the output of one command as the input of the next. Each command does one thing; the pipe chains them into something bigger. points:
- "ps aux | grep python — list processes, keep the python ones"
- "Chain as many as you want — small tools, composed"
layout: code-explain kicker: Why it works title: Three streams per process notes:
- "stdin goes in; stdout (normal output) and stderr (errors) come out."
- "A pipe wires stdout of the left into stdin of the right."
- "Redirect to files with > (overwrite) and >> (append)."
- "Send errors somewhere too, or merge them into normal output."
ls > files.txt # stdout to a file (overwrite)
echo more >> files.txt # append
make 2> errors.txt # just the errors, to a file
make &> all.txt # output and errors together
layout: default kicker: Build a pipeline title: From a log to an answer, step by step
Same three-move combo — sort | uniq -c | sort -rn — answers a huge share of "what's most common?" questions.
layout: section index: "05" kicker: Part five title: Text processing & search
layout: reference kicker: The workhorses title: grep & find groups:
- { title: "grep — search content", items: [{ term: "-i", desc: "case insensitive" }, { term: "-v", desc: "invert: lines NOT matching" }, { term: "-rn", desc: "recursive + line numbers" }, { term: "-E 'A|B'", desc: "multiple patterns" }] }
- { title: "find — locate files", items: [{ term: "-name '*.py'", desc: "by name pattern" }, { term: "-mtime -1", desc: "changed in last 24h" }, { term: "-size +100M", desc: "bigger than 100MB" }, { term: "-exec … {}", desc: "run on each match" }] }
layout: default kicker: Cut & transform title: awk & sed — the 20% you'll use
# awk — pull out columns (whitespace-split by default)
ps aux | awk '{print $1, $11}' # user + command
cut -d',' -f1 users.csv # cut: simpler column grab
# sed — search & replace
sed 's/localhost/prod-db/g' config.yaml # all on each line
sed -i 's/localhost/prod-db/g' config.yaml # edit the file in place
sed -i edits in place with no undo — test without -i first.
layout: define kicker: The missing link term: xargs definition: Some commands take arguments, not stdin. xargs turns piped output into arguments for them. points:
- "find . -name '*.py' | xargs grep TODO — search inside the matches"
- "find . -name '*.tmp' | xargs rm — act on every match"
- "Spaces in names? use -print0 with xargs -0 to stay safe"
layout: section index: "06" kicker: Part six title: The modern CLI toolkit
layout: default kicker: Structured data title: jq — slice JSON like a pro
cat todos.json | jq . # pretty-print
cat todos.json | jq '.[].title' # one field per item
cat todos.json | jq '.[] | select(.done==false)' # filter
cat repo.json | jq '{stars: .stargazers_count}' # reshape
jq is to JSON what grep/awk are to plain text — pipe any API response straight into it.
layout: panels kicker: Faster, friendlier replacements title: Tools worth installing today panels:
- { icon: "lucide:search", title: "rg (ripgrep)", items: ["fast recursive search", "respects .gitignore", "rg TODO --type py"] }
- { icon: "lucide:folder-search", title: "fd", items: ["a sane find", "fd -e yaml", "fd --changed-within 1h"] }
- { icon: "lucide:file-text", title: "bat", items: ["cat with highlighting", "line numbers + git marks", "bat config.yaml"] }
- { icon: "lucide:zap", title: "fzf", items: ["fuzzy-pick anything", "vim $(fzf)", "Ctrl+R for history"] }
layout: reference kicker: A few more title: Keep these in your back pocket items:
- { term: "tldr ", desc: "practical examples instead of dense man pages" }
- { term: "htop / btop", desc: "interactive process & resource monitor" }
- { term: "csvlens", desc: "scroll, search & sort big CSVs in the terminal" }
- { term: "lazygit / lazydocker", desc: "TUIs for git and Docker" }
- { term: "delta", desc: "git diffs with real syntax highlighting" }
- { term: "watch -n2 ", desc: "re-run a command every 2 seconds" }
layout: default kicker: Putting it together title: Real one-liners you'll reach for
# most common errors in a log
grep ERROR app.log | awk -F'[][]' '{print $2}' | sort | uniq -c | sort -rn
# busiest IPs in an access log
awk '{print $1}' access.log | sort | uniq -c | sort -rn
# pick a file with a live preview, open it
fzf --preview 'bat --color=always {}'