getapps.cafe
← Back to the Blog
mac-tipsworkflowsautomationnative-appslocal-firstmac-appsproductivity

Automating Your Mac: From Shortcuts to Shell Scripts

Shortcuts handles the clicky jobs, the shell handles anything with a loop. Where each layer earns its keep, how to schedule a job with launchd, and the Mac automation you should not build yourself.

Most Macs come with two automation layers built in, and most people use neither. Shortcuts is the one with the drag-and-drop canvas. The shell is the one that never gets tired. I spent years picking one of them for everything, then blaming the tool when the job stopped fitting. These days I split the work by shape: clicks and app actions go to Shortcuts, anything with a loop or a log goes to a script.

What Shortcuts is actually good at

Shortcuts ships with macOS, so there is nothing to install and nothing to license. You assemble a shortcut from actions, then decide where it lives: the menu bar, a Finder Quick Action, the Services menu, a keyboard shortcut, or the Share sheet. The Finder Quick Action earns its keep. It hands the files you just right-clicked straight into the shortcut, which turns "do this to whatever I select" into about four actions instead of a small program.

Apps can expose their own actions through App Shortcuts too, so a step like resizing an image can come from software you already own rather than from Apple's built-in set.

The command line reaches the same shortcuts. Open Terminal and try:

  • shortcuts list to see every shortcut you have
  • shortcuts run "Rename Screenshots" to run one by name

That second command matters more than it looks. It turns anything you built by dragging into a single line you can drop inside a shell job, a launchd job, or another shortcut.

Where it falls apart is branching. Two conditions are fine, five are unreadable, and there is no diff, no review, and no version history. When you catch yourself rebuilding the same one a third time, move the work to the other layer.

Where the shell wins

Shell commands live in plain text files, and that is most of the advantage:

  • Loops over many files. Rename 400 product photos, strip the camera prefix, sort them into folders by month.
  • Legibility later. The version you wrote in March still explains itself in September.
  • Version control. Put ~/bin in a Git repo and six months of small edits become a readable history instead of a memory test.
  • Logs and retries for slow jobs, like a nightly export that occasionally needs a second attempt.
  • Real conditionals, real error handling, real exit codes.

Those files also outlive the apps they call. When a tool gets discontinued, its built-in automation leaves with it. A shell job sits in its folder and keeps working, and you can open it to find out why it stopped.

Bridging the two

The right choice is per step rather than per machine.

  • Inside a shortcut, the Run Shell Script action passes the shortcut's input to your command as arguments. Quote everything, handle "$@" properly, because file names with spaces will find you.
  • From Terminal, call shortcuts run "Shortcut Name" and feed it a file with the input flag. Your dragged-together shortcut becomes a subroutine.
  • For app-level chores, osascript -e 'tell application "Finder" to ...' still covers jobs nothing else does, and open -a "App Name" somefile.pdf is often the entire automation.

The pattern I land on most weeks: Shortcuts for the step that needs an app, a shell job for the step that needs a loop, and launchd to make the whole thing happen while I'm making coffee.

Scheduling without a server

Everything above runs when you press something. Scheduling is how you stop pressing.

The native answer on macOS is a launch agent: a small plist in ~/Library/LaunchAgents that tells launchd when to run a command. The keys worth caring about are Label, ProgramArguments, StartCalendarInterval, and the two log paths.

Then load it:

  • launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.nightly.plist

Unload with launchctl bootout gui/$(id -u)/com.example.nightly. Check the file with plutil -lint before loading, because a malformed plist fails quietly.

Two warnings I'd give anyone starting out. launchd hands your job almost no environment, which means no PATH from your shell profile. Write absolute paths (/usr/bin/python3, not python3) or you'll lose an hour to a job that runs fine in Terminal and fails on schedule. And set the log paths. A scheduled job with no log is a mystery you scheduled for yourself.

cron still works on macOS, crontab -e and all, and it's fine for simple personal jobs. You may need to grant Full Disk Access to cron before it can read protected folders. Folder Actions are still around for "when a file lands in this folder" triggers. launchd is what I use for anything I intend to keep.

If your job starts at login instead of on a clock, the startup guide walks through login items and launch agents side by side.

Four habits that stop automations from biting

  • Dry run first. Wrap the dangerous line in echo and read what it would do. The echo pass costs a second.
  • Test on a copy. Make a folder called test, fill it with duplicates, run the job there, inspect the result.
  • chmod +x plus a .command ending makes the file double-clickable, so future you doesn't need Terminal.
  • Keep them in one place under version control. ~/bin in a Git repo, one git checkout away from undoing a bad edit.

Plenty of Mac automation is already written

Before you wire something together, check whether the job is really a timer or a search, because those are solved problems.

  • AwakeBean keeps your Mac awake on a timer, or only while chosen apps are running. It's the caffeinate wrapper most of us write by hand and then forget about.
  • CoffeeTime tracks time automatically, no start button and no forgetting to stop.
  • SnapBrew arranges windows from a shortcut, a drag, or the menu bar.
  • MultiInstance runs a second copy of the same app with its own login, settings, and data folder, which helps when the job needs two sessions at once.
  • ProjectCafe handles the case where your schedule has dependencies and moving one item should move everything waiting behind it.
  • Barista puts files, a calculator, live exchange rates, timezones, weather, and clipboard history behind one search bar. A surprising number of shell jobs exist only to avoid typing a path.
  • BlinkBrew, StretchSteam, and PosturePerk run break timers. These are the ones I always mean to build and then ignore for three weeks.

The Espresso shelf holds the small utilities you'd otherwise build yourself, and Cold Brew is where the timers and trackers live. If you'd rather press keys than wire things together, the shortcut guide covers the built-in commands that replace paid utilities.

Pick the layer that fits each step, and let real timers handle the timers. The apps I automate around are the ones that keep their work in files a command can still reach, which is the whole reason I stopped paying for tools that seal the data behind their own interface.

The café serves 126 native apps across eight shelves for one subscription, and every one of them leaves your files on your disk. Plans are on the membership page.