% man terminal

The macOS command line, end to end: Terminal.app itself, zsh, the Apple-only commands Linux never had, and the shell craft that ties it all together.

tips · 12 chapters · one self-contained file · BSD userland noted where it bites

session — you@macbook

  

Pattern not found. grep returned 1 — try a broader term, or press Esc to clear.

you@macbook ~/guide%open chapter-01

01Terminal.app, Tuned

Before touching the shell, make the window itself work for you. Terminal.app is more capable than its plain looks suggest.

clear vs. ⌘K

clear just scrolls; K truly wipes the scrollback. L clears only the last command's output — great before a screenshot.

Option-click to move the cursor

Hold and click anywhere in the current command line — Terminal sends the arrow-key presses to jump the cursor there. Editing long commands stops being an arrow-key marathon.

Marks: jump between commands

Terminal marks every prompt automatically. / jumps between previous commands' output; A selects the last command's entire output for copying.

Paste the selection, skip the clipboard

Select text anywhere in the window, then V pastes that selection at the prompt without touching your clipboard.

⌘-double-click opens URLs & paths

-double-click any URL in output to open it in your browser. Select a file path and drag it — or drop any Finder file into the window to paste its escaped path.

Profiles per purpose

Settings → Profiles: one theme for local work, a red-tinted one for SSH into production. Assign a profile per window group and you always know where a command will land.

Window groups restore your desk

Window → Save Windows as Group captures every window, tab, position, and profile. Reopen the whole arrangement from the Window menu — a project workspace in one click.

Split panes in one tab

D splits the tab horizontally so you can watch a log in the top pane while typing in the bottom. D closes the split.

Inspector & per-tab titles

I opens the inspector: rename the tab (great for many tabs), change its color profile, or see running processes in that shell.

Bell → visual, not audible

Settings → Profiles → Advanced: silence the audible bell and enable the visual one — long builds announce themselves with a screen flash and a Dock badge instead of a beep.

Mouse reporting works

Full-screen CLI apps like htop, less --mouse, and ncdu accept clicks and scroll wheels in Terminal.app — try clicking column headers in htop.

Secure Keyboard Entry

Terminal menu → Secure Keyboard Entry blocks other apps from reading your keystrokes — flip it on when typing passwords over SSH in a room full of software you don't fully trust.

you@macbook ~/guide%echo $SHELL

02zsh, Your Default Superpower

macOS has shipped zsh as the default shell since Catalina. Most people use 5% of it. Here's a bigger slice.

History search that reads your mind

R incrementally searches history as you type. Better: type the start of a command, then press — with history-search bound, it only cycles matches with that prefix.

!! and friends

sudo !! reruns the last command with sudo. !$ is the last argument of the previous command (mkdir proj && cd !$); !* is all of them. Press Tab after typing to expand before running.

Alt-. cycles last arguments

Esc then . (or . with “Use Option as Meta key” enabled in Terminal settings) inserts the previous command's last argument — press again to walk back through history.

Globbing beyond the asterisk

**/*.py matches recursively with no find needed. *(.) matches only plain files, *(/) only directories, *(.om[1]) the newest file. zsh glob qualifiers are a query language.

Suffix aliases: open by extension

alias -s md=open means typing notes.md alone opens it. Map html=open, pdf=open, py=python3 — filenames become commands.

Global aliases expand anywhere

alias -g L='| less' and alias -g G='| grep' let you write ps aux G octave L. Use sparingly; enjoy fully.

Named directories

hash -d proj=~/Sites/guides makes ~proj a real path everywhere — cd ~proj, cp file ~proj/. Your prompt shows ~proj instead of the long path, too.

cd without cd

setopt autocd — type a directory name alone to enter it. cd - returns to the previous directory; dirs -v lists the stack and cd -2 jumps two back.

zmv: batch rename with patterns

autoload zmv, then zmv '(*).jpeg' '$1.jpg' renames en masse. Add -n first for a dry run that prints what would happen.

Shared, timestamped history

In .zshrc: setopt share_history inc_append_history extended_history hist_ignore_dups and a big HISTSIZE. Every tab shares one searchable, timestamped history.

Spelling correction

setopt correct — mistype gerp and zsh offers “correct to grep?” One keystroke accepts. Surprisingly rarely wrong.

Tab-completion is configurable

autoload -Uz compinit && compinit enables rich completion: kill <Tab> lists processes, ssh <Tab> completes known hosts, brew <Tab> completes subcommands.

Instant timing for slow commands

REPORTTIME=5 in .zshrc auto-prints timing stats for anything that runs longer than 5 seconds — free profiling with zero effort.

vared: edit variables live

vared PATH opens the variable in an inline editor — inspect and fix your PATH without echo-and-retype gymnastics.

~/.zshrc — a sane starting point
# --- History: huge, shared, deduplicated ---
HISTSIZE=100000
SAVEHIST=100000
setopt share_history inc_append_history extended_history hist_ignore_dups

# --- Quality of life ---
setopt autocd correct interactive_comments
autoload -Uz compinit && compinit
autoload zmv

# --- Aliases that earn their keep ---
alias ll='ls -lahG'
alias brewup='brew update && brew upgrade && brew cleanup'
alias path='echo $PATH | tr ":" "\n"'
alias -g L='| less'

# --- Prefix history search on the arrow keys ---
autoload -U up-line-or-beginning-search down-line-or-beginning-search
zle -N up-line-or-beginning-search
zle -N down-line-or-beginning-search
bindkey '^[[A' up-line-or-beginning-search
bindkey '^[[B' down-line-or-beginning-search
Reload without restarting: after editing, run source ~/.zshrc. If startup ever feels slow, zsh -i -c exit under time tells you what your config costs.

you@macbook ~/guide%ls /usr/bin | grep -i apple

03Commands Only a Mac Has

Apple ships dozens of commands with no Linux equivalent — bridges between Unix and the Mac experience. These are the ones worth memorizing.

open — the universal launcher

open . current folder in Finder · open -R file reveal it · open -a Preview img.png pick the app · open -e notes.txt TextEdit · open -g without stealing focus · open https://… default browser.

pbcopy / pbpaste

The clipboard as a pipe. cat script.sh | pbcopy, pbpaste > dump.txt, or round-trip it: pbpaste | sort -u | pbcopy deduplicates whatever you copied.

mdfind — Spotlight's engine

mdfind "riemann zeta" full-text search at index speed · mdfind -name thesis.pdf by name · mdfind -onlyin ~/Sites query scoped · mdls file shows all metadata Spotlight indexed.

sips — image toolbox

sips -s format png in.heic --out out.png convert · sips -Z 1200 *.jpg resize to fit · sips -r 90 img.png rotate · sips -g pixelWidth -g pixelHeight img.png dimensions.

textutil — document converter

textutil -convert html paper.docx · -convert txt *.rtf · -cat rtf ch1.rtf ch2.rtf -output book.rtf concatenates. Handles docx, rtf, html, txt, and more.

caffeinate — insomnia on demand

caffeinate -d keeps the display on until Ctrl-C · -t 7200 for two hours · caffeinate -i ./build.sh holds off idle sleep exactly as long as the build runs.

networkquality — Apple's speed test

One word, no website: download, upload, and responsiveness (RPM) under load. -v for detail, -s to test up and down sequentially.

screencapture — scriptable shots

screencapture -c to clipboard · -i interactive crosshair · -T 5 delay · -x silent · screencapture -i -c && pbpaste-adjacent tricks compose with everything else.

say — text to speech

say "tests passed" as a build alarm · say -v '?' lists voices · say -v Samantha -o audio.aiff -f essay.txt renders a whole file to audio.

qlmanage — Quick Look from the shell

qlmanage -p document.pdf pops a Quick Look preview without opening an app — handy inside scripts to eyeball a generated file.

afplay & the system sounds

afplay /System/Library/Sounds/Glass.aiff — every alert sound lives there. Append to long commands as an audible completion bell.

ditto — the Finder-faithful copier

ditto src/ dst/ copies preserving extended attributes, resource forks, and ACLs; ditto -c -k --keepParent folder out.zip zips exactly like Finder's Compress.

plutil — plist surgeon

plutil -p prefs.plist pretty-prints binary plists · -convert xml1 makes them editable · -lint validates after hand-editing. Pairs with defaults.

osascript — AppleScript one-liners

osascript -e 'display notification "done" with title "make"' · -e 'set volume output volume 30' · -e 'tell app "System Events" to sleep'. The GUI, scriptable.

shortcuts — run your Shortcuts

shortcuts list then shortcuts run "Resize for Web" -i photo.png — every Shortcut you've built becomes a command, composable with pipes.

softwareupdate & sw_vers

sw_vers prints the macOS version; softwareupdate --list pending updates; sudo softwareupdate -ia installs all — essential over SSH.

system_profiler — the spec sheet

system_profiler SPHardwareDataType chip, memory, serial · SPPowerDataType battery health and cycles · -listDataTypes shows every report available.

security — talk to the Keychain

security find-internet-password -s github.com -w prints a stored password (after Keychain approval). Scripts can fetch secrets without hardcoding them.

you@macbook ~/guide%defaults read | wc -l

04The defaults Cookbook

Every checkbox in System Settings is a plist key underneath — plus hundreds that never got a checkbox. defaults write is the settings behind the settings.

Read before you write

defaults read com.apple.dock dumps every key the Dock has. Change a setting in the GUI, diff the output, and you've discovered the key it flips — the standard research technique.

Domains and where they live

User settings live in ~/Library/Preferences/*.plist, one file per domain. defaults domains | tr ',' '\n' lists everything configurable on your Mac.

Every change is reversible

defaults delete com.apple.dock autohide-delay returns any key to factory behavior. Note the key before experimenting and nothing is ever permanent.

Restart the right process

Prefs are cached: Dock tweaks need killall Dock, Finder tweaks killall Finder, menu-bar items killall SystemUIServer. Logging out catches everything else.

defaults — the greatest hits
# Screenshots: choose a home, and a lighter format
mkdir -p ~/Screenshots
defaults write com.apple.screencapture location ~/Screenshots
defaults write com.apple.screencapture type jpg
killall SystemUIServer

# Dock: instant, fast, honest
defaults write com.apple.dock autohide-delay -float 0
defaults write com.apple.dock autohide-time-modifier -float 0.35
defaults write com.apple.dock show-recents -bool false
killall Dock

# Finder: extensions, path bar, folders first, quit menu item
defaults write NSGlobalDomain AppleShowAllExtensions -bool true
defaults write com.apple.finder ShowPathbar -bool true
defaults write com.apple.finder _FXSortFoldersFirst -bool true
defaults write com.apple.finder QuitMenuItem -bool true
killall Finder

# Keyboard: repeat like a terminal user
defaults write NSGlobalDomain KeyRepeat -int 2
defaults write NSGlobalDomain InitialKeyRepeat -int 15
defaults write NSGlobalDomain ApplePressAndHoldEnabled -bool false

# Save dialogs: always expanded
defaults write NSGlobalDomain NSNavPanelExpandedStateForSaveMode -bool true

# TextEdit: plain text by default (writers of HTML, rejoice)
defaults write com.apple.TextEdit RichText -bool false

# Undo any of it
defaults delete com.apple.finder QuitMenuItem && killall Finder
Log out if in doubt. A handful of keys only apply at login. And treat random defaults snippets from the internet like any other code: read them, understand the domain and key, then run.

you@macbook ~/guide%diskutil list

05Files, Disks & APFS

From extended attributes to disk images and snapshots — the storage layer, demystified from the prompt.

ls has Mac-only flags

ls -l@ shows extended attributes (that's where quarantine lives) · ls -lO shows BSD flags like hidden · ls -lh human sizes. -G for color, or export CLICOLOR=1.

xattr — the attribute layer

xattr file lists attributes · xattr -p com.apple.quarantine file prints one · xattr -d com.apple.quarantine file removes the download flag (only for software you trust) · -c clears all.

chflags hidden

chflags hidden secret-folder hides it from Finder without renaming; nohidden reverses. Purely cosmetic — the shell always sees it.

du, df, and the truth about space

du -sh * | sort -h ranks a folder's contents · df -h / free space. APFS purgeable space makes Finder and df disagree — both are “right.”

diskutil — Disk Utility's engine

diskutil list every disk and volume · diskutil info / details · diskutil eraseDisk JHFS+ Backup /dev/diskN formats (triple-check N!) · diskutil apfs list the APFS container view.

hdiutil — disk images from thin air

hdiutil create -size 500m -fs APFS -volname Vault -encryption AES-256 vault.dmg makes an encrypted disk image — a password-protected folder, essentially. hdiutil attach/detach mounts it.

tmutil — Time Machine's CLI

tmutil listlocalsnapshots / shows APFS snapshots · tmutil deletelocalsnapshots 2026-07-01-120000 frees space · tmutil compare diffs a snapshot against now.

Snapshot before you experiment

tmutil localsnapshot creates an instant APFS snapshot of the whole volume — a free checkpoint before a risky cleanup or config spree.

rsync for real copying

rsync -avh --progress src/ dst/ resumes interrupted copies and syncs only changes. Trailing slash on src/ means “contents of” — its most-forgotten detail.

find, when Spotlight can't

find . -name '*.log' -size +50M -mtime +30 — unindexed volumes, system paths, and precise predicates. -delete at the end acts on matches (dry-run without it first).

Trash, not rm

rm is forever. For interactive work, alias a mover: trash(){ mv "$@" ~/.Trash/; } — recoverable deletes from the shell. Keep rm for scripts that mean it.

zip like Finder does

Finder's Compress is really ditto -c -k --sequesterRsrc --keepParent folder out.zip — resource forks preserved, Windows-safe. Plain zip -r works too when metadata doesn't matter.

you@macbook ~/guide%ping -c1 macbook.local

06Networking From the Prompt

Diagnose Wi-Fi, interrogate DNS, and find out exactly who is listening on what — no menu diving.

Your IP, instantly

ipconfig getifaddr en0 prints the Wi-Fi IP and nothing else — perfect in scripts. Public IP: curl -s ifconfig.me.

Who's listening on my ports?

sudo lsof -iTCP -sTCP:LISTEN -n -P lists every listening process with its port. lsof -i :8080 answers the eternal “what's squatting on my dev port?”

networksetup — Settings via CLI

networksetup -listallhardwareports maps ports to devices · -getairportnetwork en0 current Wi-Fi name · -setairportpower en0 off toggles Wi-Fi · -setdnsservers Wi-Fi 1.1.1.1 changes DNS.

DNS, as macOS actually sees it

scutil --dns shows the real resolver configuration (macOS doesn't use /etc/resolv.conf the Unix way). Flush the cache: sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder.

dig for the truth

dig example.com +short just the answer · dig @1.1.1.1 example.com ask a specific server · dig -x 8.8.8.8 reverse lookup. When browsers lie, dig doesn't.

.local names beat IP addresses

Every Apple device answers Bonjour: ssh you@macbook-air.local, ping printer.local. dns-sd -B _ssh._tcp browses everything advertising SSH on the LAN.

Wi-Fi password, retrieved

security find-generic-password -wa "NetworkName" prints a saved Wi-Fi password after Keychain approval — for that guest who needs it while your phone's away.

curl beyond downloading

curl -I url headers only · -L follow redirects · -o file save · -w "%{time_total}\n" -s -o /dev/null url times a request — a one-line uptime probe.

~/.ssh/config is a superpower

Define Host mini with HostName, User, and Port once — then ssh mini forever. Add ControlMaster auto and multiplexed connections make repeat SSH instant.

Serve this folder, right now

python3 -m http.server 8000 shares the current directory on your LAN — the fastest way to hand a file to another device or test a static site like this one.

mtr-style tracing, built in

traceroute example.com shows the path; ping -c 5 gateway.local the latency. For continuous combined stats, brew install mtr is worth it.

Watch traffic with nettop

nettop -m tcp is Activity Monitor's network tab in the terminal: live per-process connections and byte counts. Press c to collapse, q to quit.

you@macbook ~/guide%top -o cpu

07Processes, Power & Performance

See what the machine is really doing — and make it stop doing the parts you don't like.

ps, filtered fast

ps aux | grep -i octave classic · better: pgrep -fl octave finds PIDs by name, and pkill -f octave ends them — no PID copying.

Signals, in order of politeness

kill PID sends TERM (please quit) · kill -INT is Ctrl-C's signal · kill -9 is KILL — no cleanup, last resort. Try polite first; files stay intact.

top, the macOS dialect

top -o cpu sorts by CPU · -o mem by memory · press q to quit. For humane colors and scrolling, brew install htop or btop.

What's preventing sleep?

pmset -g assertions names every process holding the Mac awake. pmset -g log | grep -i wake explains mystery midnight wakes.

powermetrics — the deep gauge

sudo powermetrics --samplers cpu_power -n 1 shows per-cluster power on Apple Silicon: E-cores vs P-cores, frequency, and watts. The honest answer to “what's draining the battery?”

Memory pressure, not memory used

memory_pressure prints the system's own verdict. macOS keeping RAM full is by design; only sustained pressure (and swap growth in vm_stat) means trouble.

fs_usage — who touches what

sudo fs_usage -w -f filesys | grep Desktop streams every file operation live — the tool for “what keeps writing to my disk?”

time everything

Prefix any command with time for wall/user/sys breakdown. zsh's built-in prints a compact line; REPORTTIME=5 automates it for slow commands.

Run it nicer

nice -n 20 ./render.sh runs a heavy job at lowest priority so the UI stays smooth. On Apple Silicon, low-priority work migrates to efficiency cores.

Jobs: &, ⌃Z, fg, bg

Append & to background a task · Z suspends the current one · bg resumes it in background, fg brings it back · jobs -l lists them all.

Keep it alive after logout

nohup long_job & survives closing the tab. Better for anything serious: brew install tmux — detach with B D, reattach anytime with tmux attach.

The unified log, tamed

log show --last 5m --predicate 'eventMessage CONTAINS[c] "error"' queries the system log; log stream --process Dock follows one process live. Console.app, scriptable.

you@macbook ~/guide%grep -rn "totient" ~/Sites

08Text Wrangling (BSD Edition)

The classic Unix text tools are all here — in their BSD dialects. Knowing the two or three places they differ from Linux saves real debugging time.

The BSD sed -i trap

On macOS, in-place editing needs an explicit backup suffix: sed -i '' 's/old/new/g' file (empty string = no backup). Linux's bare sed -i errors here — the #1 cross-platform script bug.

grep, then ripgrep

grep -rn "pattern" . recursive with line numbers · -i case-blind · -v invert · -C3 context. Then brew install ripgrep: rg pattern is faster, skips .git, respects .gitignore.

awk for columns

ps aux | awk '{print $2, $11}' picks fields · awk -F: '{print $1}' /etc/passwd custom delimiter · awk '{s+=$1} END {print s}' sums a column. Eighty percent of awk is these three moves.

sort | uniq -c | sort -rn

The classic frequency pipeline: count duplicate lines, most common first. history | awk '{print $2}' | sort | uniq -c | sort -rn | head reveals your true most-used commands.

cut and paste (the commands)

cut -d, -f2 data.csv extracts a CSV column · cut -c1-8 character ranges · paste -d, a.txt b.txt zips files side by side.

jq — JSON's best friend

brew install jq, then curl -s api.url | jq '.items[].name'. Even bare | jq . as a pretty-printer earns its keep daily.

tail -f and friends

tail -f app.log follows a growing file · tail -n +2 data.csv skips the header row · head -c 1k file | xxd peeks at binary headers.

tr — tiny transforms

tr ':' '\n' <<< "$PATH" one path entry per line · tr -d '\r' strips Windows line endings · tr '[:lower:]' '[:upper:]' shouts.

column -t aligns anything

Pipe messy delimited output through column -t for instant aligned tables: mount | column -t is suddenly readable.

diff, visually

diff -u old new unified patches · diff -r dir1 dir2 whole trees · opendiff old new launches Apple's graphical FileMerge if Xcode tools are installed.

wc counts more than words

wc -l < file lines (the < suppresses the filename) · ls | wc -l files in a folder · curl -s url | wc -c bytes of a page.

Want GNU behavior? Opt in.

brew install coreutils gnu-sed installs GNU tools prefixed with g: gsed, gdate, gls. Scripts that need Linux semantics call the g-versions explicitly — portable and unambiguous.

you@macbook ~/guide%make things | compose well

09Shell Craft — Composing Commands

The Unix philosophy in practice: small tools, glued with pipes, redirection, and substitution into exactly the tool you needed.

&&, ||, and ;

build && deploy runs deploy only on success · cmd || echo failed only on failure · a ; b runs both regardless. Chain them: make && say done || say broken.

Redirection, precisely

> out.txt overwrite · >> append · 2>errors.log just stderr · >out 2>&1 (or zsh's &>) both · 2>/dev/null silence the complaints.

tee — watch and save at once

./build.sh | tee build.log streams to screen and file. tee -a appends; | tee /dev/tty | wc -l lets you see data mid-pipeline.

Command substitution

$(...) drops a command's output into another: cd "$(mdfind -name 'Four Threads' | head -1 | xargs dirname)" — jump to wherever a file lives.

Process substitution

diff <(ls dir1) <(ls dir2) compares two commands' outputs as if they were files — no temp files, no cleanup.

xargs — output becomes arguments

mdfind -name '.DS_Store' | xargs ls -l · with spaces in names, use find . -name '*.bak' -print0 | xargs -0 rm · -n1 runs once per item, -P4 parallelizes.

Loops at the prompt

for f in *.heic; do sips -s format jpeg "$f" --out "${f%.heic}.jpg"; done — the ${f%.ext} strips the suffix. zsh shorthand: for f (*.heic) echo $f.

Functions > complex aliases

Anything needing arguments belongs in a function: mkcd(){ mkdir -p "$1" && cd "$1"; } in .zshrc. Aliases are for fixed strings; functions are for logic.

Exit codes speak

echo $? shows the last command's status — 0 is success, anything else is a specific failure. Scripts branch on it; set -e makes a script stop at the first error.

Braces expand

mkdir -p project/{src,docs,tests} makes three dirs · cp config.yml{,.bak} expands to config.yml config.yml.bak — the fastest backup in the shell.

The heredoc

cat > file <<'EOF' … EOF writes multi-line files from the prompt — quoting 'EOF' keeps $ literal. The workhorse of scripted file assembly (this very guide was built with them).

Watch anything change

No built-in watch on macOS: while true; do clear; df -h /; sleep 5; done — or brew install watch for the real thing.

you@macbook ~/guide%printf '\e[35mhello\e[0m\n'

10Color, Prompts & Play

ANSI escapes drive every color in the terminal — and a few commands exist purely to make the machine more pleasant company.

Color output, everywhere

export CLICOLOR=1 colors ls permanently · grep --color=auto highlights matches · most tools honor --color or detect a terminal automatically.

Anatomy of an escape code

\e[31m starts red, \e[0m resets. 30–37 foreground, 40–47 background, 1 bold: printf '\e[1;32mOK\e[0m\n' prints a bold green OK. Use the lab below to explore.

A prompt with git awareness

zsh's vcs_info puts the current branch in your prompt natively — or brew install starship for a fast, informative prompt configured in one TOML file.

cal, date, and friends

cal this month · cal 2027 a whole year · date +%Y-%m-%d formatted (BSD date syntax differs from GNU — gdate from coreutils if a script needs Linux flags).

bc & units — math at the prompt

echo "scale=10; 4*a(1)" | bc -l computes π · units "26.2 miles" km converts anything to anything, with dimensional analysis for free.

The classics, via brew

brew install cowsay fortune sl cmatrix · fortune | cowsay wisdom, delivered · sl punishes mistyping ls with a steam locomotive · cmatrix for ambiance.

banner, in system voice

banner -w 60 hi prints old-school giant letters. Combine with say -v Zarvox "greetings" and the Mac is officially having fun.

Screensaver as wallpaper trick

open -a ScreenSaverEngine starts the screensaver on demand — bindable via Shortcuts to a hotkey for instant “stepping away” mode.

ANSI Color Lab

Click a color — the escape code and a ready-to-run printf are generated below.

sample text

you@macbook ~/guide%launchctl list | grep local

11Automation & Scheduling

launchd is macOS's init and cron in one. Teach it a plist and your Mac runs your errands — on schedule, on wake, or whenever a folder changes.

launchd vs. cron

cron technically works, but launchd runs jobs missed during sleep, manages daemons, and can watch paths. User jobs live in ~/Library/LaunchAgents/*.plist.

Load, list, kickstart

launchctl load ~/Library/LaunchAgents/com.you.job.plist registers a job · launchctl list | grep com.you confirms · launchctl kickstart -k gui/$(id -u)/com.you.job runs it now.

WatchPaths: folder-triggered jobs

A WatchPaths key in the plist runs your script the moment a folder's contents change — auto-convert images dropped into an inbox, or auto-deploy when a build lands.

Shortcuts as cron jobs

Anything you can build in Shortcuts, launchd can schedule: point the plist's ProgramArguments at shortcuts run "Nightly Cleanup" — GUI automation on a timer.

osascript = the GUI's API

osascript -e 'tell app "Music" to playpause' · -e 'tell app "Finder" to empty trash' · display dialogs, resize windows, read Safari's current URL — AppleScript reaches where POSIX can't.

Automator still earns its keep

Folder Actions attach a workflow to a directory via right-click → Services → Folder Actions Setup. Zero code, surprisingly robust — and it can embed shell scripts inside.

pmset schedules wake & sleep

sudo pmset repeat wakeorpoweron MTWRF 07:45:00 wakes the Mac before you sit down; sudo pmset repeat cancel clears it. Pair with a launchd job for pre-warmed mornings.

Notifications from scripts

End long jobs with osascript -e 'display notification "render complete" with title "ffmpeg" sound name "Glass"' — banners beat staring at a progress bar.

~/Library/LaunchAgents/com.you.backup.plist — a daily job, annotated
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>            <string>com.you.backup</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/zsh</string>
    <string>-c</string>
    <string>rsync -a ~/Sites/guides/ ~/Backups/guides/</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Hour</key>   <integer>21</integer>
    <key>Minute</key> <integer>30</integer>
  </dict>
  <key>StandardOutPath</key>  <string>/tmp/backup.log</string>
  <key>StandardErrorPath</key><string>/tmp/backup.err</string>
</dict>
</plist>

# Then, once:
#   launchctl load ~/Library/LaunchAgents/com.you.backup.plist
# Verify with: launchctl list | grep com.you

you@macbook ~/guide%man hier

12Safety Nets & Good Habits

The terminal assumes you mean what you type. These habits make sure that's true — and soften the landing when it isn't.

Read before running

type cmd tells you if a command is a builtin, alias, function, or binary — and where. which -a python3 lists every match on PATH in order. Know what will actually execute.

tldr beats man for recall

brew install tldrtldr rsync shows the five examples you actually wanted. man remains the authority; tldr is the cheat sheet.

Dry runs first

Destructive tools usually offer a rehearsal: rsync -n, zmv -n, brew upgrade --dry-run, find … -print before -delete. Rehearse, read, then run.

Quote your variables

rm "$dir"/*.tmp — always. An unquoted variable containing a space becomes two arguments, and “Photos 2024” becomes Photos and 2024. Quoting is not optional; it's grammar.

Careful with sudo + redirects

sudo echo x > /etc/file fails — the redirect runs as you, not root. The pattern that works: echo x | sudo tee /etc/file (add -a to append).

SIP is not your enemy

System Integrity Protection keeps even root from altering system files. If a tutorial's first step is disabling SIP, close the tab — modern workflows never need it.

shellcheck your scripts

brew install shellcheck — it catches quoting bugs, useless cats, and portability traps with explanations. Every script deserves one pass.

set -euo pipefail

The strict-mode opener for scripts: stop on errors (-e), on unset variables (-u), and fail pipelines if any stage fails. Loud early failures beat silent corruption.

Snapshot, then experiment

Before a config spree: tmutil localsnapshot. Before editing a file: cp config.yml{,.bak}. Undo buttons don't exist here unless you make them.

The PATH sanity check

echo $PATH | tr ':' '\n' — on Apple Silicon, /opt/homebrew/bin should appear before /usr/bin. Most “wrong version runs” mysteries end here.

history is your lab notebook

history 0 | grep defaults recalls every defaults tweak you've ever made. With extended_history on, fc -lid -20 shows timestamps too.

Know the map: man hier

man hier documents the whole filesystem layout — what belongs in /usr/local vs /opt vs ~/Library. Ten minutes of reading, permanent orientation.

The meta-skill: the terminal rewards composition. Each chapter here is a vocabulary; fluency is combining them — an mdfind into an xargs into a sips, scheduled by launchd, announced by say. Build sentences.