Bash script automation in macOS

Scheduling bash scripts to be run at login using `launchd`

Bash script automation in macOS

Photo by Lucas Hoang on Unsplash

Introduction

There are many scenarios in which bash scripts can be used to automate processes. Sometimes, I like to test things in Python. For instance, sometimes one of my colleagues asks me if something is possible in Python or how it is done. In those cases, I don't want to create a new project, but rather to create a new folder, throw a bunch of python files in, and figure out things. After some time, these test projects make a big mess in my directories, which I don't like.

To fix this, I made a bash script to remove all the files and folders that I have created under "temp" every time I boot my Mac. This blog post will describe how I managed to use macOS launchd to schedule a bash script to delete all the test files and folders.

What is launchd?

As a Linux user or at least someone who has worked with Linux, you may be familiar with systemd. launchd is exactly the same, but it can do more like a job scheduler (corn replacement) and it is much more reliable than systemd.

1. How to interface launchd?

Anyhow, to interact with Launchd, use launchctl in your bash shell. Same as the systemd and the systemctl commands, isn't it? You can see more information about launchctl by typing man launchctl in your terminal window.

2. What launchd configuration files look like?

Having learned about the concept of launchd and its interface launchctl, it is now time to understand how we can define a new configuration for this. In contrast to systemd, which uses TOML config files, launchd makes use of plist files to define a configuration. Below is an example:

<?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.example.hello</string>
    <key>ProgramArguments</key>
    <array>
        <string>hello</string>
        <string>world</string>
    </array>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>

3. Where to save launchd configuration files?

There are three locations in which you can store a launchd configuration file. If your application is supposed to run as a daemon service, you can store it under /System/Library/LaunchDaemons/. If your application is supposed to run whenever a user logs in, you should store it under /Library/LaunchAgents. Finally, If your application is supposed to run whenever a specific user logged in it must be stored under ~/Library/LaunchAgents.

Creating the automation

It's time to set up the automation now that we know how launchd works. Let's create the bash script file first:

rm -rf $HOME/projects/temp && mkdir $HOME/projects/temp

You need to save this script to ~/.scripts/loginscript.sh. The script will delete the files and folders in the temp folder every time you run it. We will now need to create the config file for Launchd so the script runs every time we log in.

<?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.user.loginscript</string>
    <key>ProgramArguments</key>
    <array>
        <string>/bin/zsh</string>
        <string>PATH-TO-HOME-DIRECTORY/.scripts/loginscript.sh</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
</dict>
</plist>

Let's save this to ~/Library/LaunchAgents/com.user.loginscript.plist. Before registering it to launchd we first need to get the user id as it needs by launchd:

id -u

Copy the output and run the command below to register the configuration to launchd:

sudo launchctl bootstrap gui/<USER-ID> ~/Library/LaunchAgents/com.user.loginscript.plist

References