Since I integrated fzf into my workflow almost four years back, I make use of my shell history quite heavily. Binding Ctrl+R
to fuzzy search using fzf has been a game changer for me.
Being able to quickly search through my shell history allows me to focus on the task at hand instead of remembering or noting down the commands I ran to do something. Given my dependence on shell history, I figured it was a good idea to back it up to iCloud periodically.
Let's look at the command we want to run first. Please note that it uses absolute paths rather than relative paths.
#!/usr/bin/env bash
cp /Users/manojkarthick/.zsh_history /Users/manojkarthick/Library/Mobile\ Documents/com~apple~CloudDocs/backups/
All Macs that have iCloud enabled use, the Library/Mobile\ Documents/com~apple~CloudDocs/
folder to sync files from iCloud to your local disk. I created a folder backup
under that directory to store my shell history.
The next part is to figure out how to run the command periodically. On a Mac, Launchd is the recommended solution rather than cron.
Launchd has additional niceties such as running missed tasks — for example, when logged out or when the machine is shut down — are run when you login the next time while collapsing the failed runs. Launchd has two type of jobs — Agents and Daemon — for our usecase, we need to use a User Agent. I would highly recommend checking out this fantastic website for more details on launchd and the various configuration options available.
Launchd uses XML to specify the configuration. The snippet below configures our copy job:
<?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>launched.local.shell-history</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/manojkarthick/jobs/copy-shell-history.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
Let's look at the various parameters to understand how it works -
Label
is the unique name for the applicationProgramArguments
is an array that specifies the command to run. In our case, we want to run thecopy-shell-history.sh
script.StartInterval
specifies how often we want to run the agent. In our case, we want it to run every 300 seconds (5 minutes).RunAtLoad
is used to start the job as soon as it has been loaded. For agents this means execution at login.
Now, let's copy the launchd config and load the agent. It's convention to use reverse domain name notation for the agents - I like to use the launched.local
prefix for my jobs.
cp </path/to/xml/file> ~/Library/LaunchAgents/launched.local.shell-history
launchctl load ~/Library/LaunchAgents/launched.local.shell-history
To test if the agent is working as expected, let's run it once. Using the launchctl start
command will run the application once irrespective of the execution conditions. Once the job is completed, you should see the .zsh_history
file in your iCloud folder on Finder.
launchctl start ~/Library/LaunchAgents/launched.local.shell-history
If you're interested in a GUI Application to configure launchd daemons and agents, I've heard good things about LaunchControl. I haven't used it personally since my needs are pretty simple.
Cheers!