This series is an excerpt from the first chapter of my upcoming book “Scripting macOS” which will teach you to use and create shell scripts on macOS.
- Part 1: First Script
- Part 2: The Script File
- Part 3: The Code
- Part 4: Running the Script
- Part 5: Lists of Commands
- Part 6: Turning it off and on again
- Part 7: Download and Install Firefox
I will publish one part every week. Enjoy!
Follow this blog or the Twitter account for updates on the book’s progress!
Download and Install Firefox
To further illustrate the progress from the idea of a workflow to a working script, let us look at another, more involved example.
To download and install the latest version of Firefox a user has to go to the Firefox website and download the latest version, which will come as a disk image (dmg) file. Then the user needs locate the dmg in the ~/Downloads
folder and open it to mount the virtual disk image. Finally, they need to copy the Firefox application from the virtual disk to the Applications folder.
When we want to automate the task ‘Download and Install Firefox,’ we have the following steps:
- download latest Firefox disk image
- mount downloaded disk image
- copy Firefox application to /Applications
- unmount disk image
From this list of steps, we can build the first ‘frame’ of our script:
#!/bin/zsh
# Download Firefox
#
# downloads and installs the latest version of Firefox
# download latest Firefox disk image
# mount downloaded disk image
# copy Firefox application to /Applications
# unmount disk image
This breaks the workflow into smaller pieces, that we will now tackle individually.
Download from the Command Line
You can use the curl
command to download data in the command line. The curl
command is very complex and has many options. We will only discuss the few options that we require for our task here. As always, you can find a detailed description of the curl
command and its options in the curl man page.
The URI to download the latest Firefox ishttps://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US
However, when you try to curl
this URI, you only get the following:
> curl "https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US"
<a href="https://download-installer.cdn.mozilla.net/pub/firefox/releases/86.0.1/mac/en-US/Firefox%2086.0.1.dmg">Found</a>.
This is a re-direction, that is commonly used to have a single URI, that is redirected to different final URIs, so that when the software updates, the same URI always returns the latest version.
We can tell curl
to follow these redirections with the --location
option.
By default, the curl
command will output the download to standard out. To save the download to a file, we can use the --output
option with a file name.
> curl --location "https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US" --output Firefox.dmg
This command will download the latest Firefox disk image to a file named Firefox.dmg
in your current working directory. We can use this as our first step:
#!/bin/zsh
# Download Firefox
#
# downloads and installs the latest version of Firefox
# download latest Firefox disk image
curl --location "https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US" \
--output Firefox.dmg
# mount downloaded disk image
# copy Firefox application to /Applications
# unmount disk image
Note: Like many other command line tools, curl has short and long options. The short options for –location and –output are -L and -o.
Short options are convenient in the interactive shell, as they save typing and reduce the potential for typos. But they are much less readable, and you usually have to look up their function in the documentation. For that reason, I recommend using the long, descriptive options in scripts.
Working with Disk Images
The command line tool to work with disk image (dmg) files on macOS is hdiutil
. This is also a very powerful command with many verbs and options. You can find all the detail in the hdiutil
man page.
To mount a disk image, use the attach
verb:
> hdituil attach Firefox.dmg
This will output some information and mount the virtual disk. The last line ends with the path to the mounted virtual disk /Volumes/Firefox
.
By default, you can see the mounted volume in Finder. We do not really need the disk image to appear in Finder while the script is running. We can suppress this behavior with the -nobrowse
option.
Since we are only going to read from the disk image, we can tell hdiutil
to mount the dmg in readonly mode with the -readonly
option. This speeds things up a bit.
> hdiutil attach Firefox.dmg -nobrowse -readonly
You can unmount or eject the virtual disk with
> hdiutil detach -force /Volumes/Firefox
The -force
option will unmount the disk image, even when another process is still using it.
Thehdiutil
command covers two of our steps, so we can fill them in:
#!/bin/zsh
# Download Firefox
#
# downloads and installs the latest version of Firefox
# download latest Firefox disk image
curl --location "https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US" \
--output Firefox.dmg
# mount downloaded disk image
hdiutil attach Firefox.dmg -nobrowse -readonly
# copy Firefox application to /Applications
# unmount disk image
hdiutil detach /Volumes/Firefox -force
Copying the Application
When you manually install Firefox the disk image shows you a nice graphic that reminds you to drag the app to the Applications folder. Once the disk image is mounted, the cp
command can be used to do this in the shell:
> cp -R /Volumes/Firefox/Firefox.app /Applications/
This provides the last missing step in our script:
#!/bin/zsh
# Download Firefox
#
# downloads and installs the latest version of Firefox
# download latest Firefox disk image
curl --location "https://download.mozilla.org/?product=firefox-latest-ssl&os=osx&lang=en-US" \
--output Firefox.dmg
# mount downloaded disk image
hdiutil attach Firefox.dmg -nobrowse -readonly
# copy Firefox application to /Applications
echo "copying Firefox to /Applications"
cp -R /Volumes/Firefox/Firefox.app /Applications/
# unmount disk image
hdiutil detach /Volumes/Firefox/ -force
You can now test the script. If Firefox is running, you want to quit it before you run the script. You may also want to delete the existing copy of Firefox from the Applications folder, to be sure that your script is doing the work.
Lists of Commands—Conclusion
We have been able to automate a fairly complex workflow with a script of four commands.
To be perfectly honest, this script (as well as all the others we have built so far) is not complete yet.
A ‘proper’ script needs to be able to react to errors that occur. In our example, imagine the download fails. The script should be able to detect the failure before it overwrites the installed, functional Firefox application.
We will get to this kind of error handling later.
Nevertheless, this script is already useful in its current form. You can try to adapt this script to work with some other software you can download as a disk image.
You can also add extra commands that
- delete the downloaded disk image at the end
- open the newly installed Firefox app after installation
- quit or kill the Firefox process before copying the new version
In the book “Scripting macOS”, you will learn more scripting techniques, and we will re-visit some of these sample scripts and keep improving them.
Follow this blog or the Twitter account for updates on the book’s progress!
Note: After using different variations of these kinds of workflows, I did put together a more generic script to download and install various kinds of software, called ‘Installomator.’ You can see the script at its open source repository on GitHub.