Catalina vs. Mojave for Scripters
As the author of a book filled with scripts for the Macintosh, I was worried about the differences between scripting for Mojave and scripting for Catalina. When testing the Catalina update of the book, it turned out there wasn’t much difference. For the most part, what worked in Mojave worked in Catalina.
The most obvious changes, of course, were those having to do with iTunes. Catalina broke the iTunes app into several apps. The new app for music is called Music. This means that AppleScript scripts need to change from:
- tell application "iTunes"
to
- tell application "Music"
The application id has also changed, from “com.apple.iTunes” to “com.apple.Music”.
The application id is useful if, for example, you want to do some stuff if the Music app is running, but skip out if the Music app is not running. The normal means of talking to an application in AppleScript will start the application up, which means it can’t be used to check whether the application is running: it always will be, after using “tell” on it. The application id can check the status of the app without opening the app:
[toggle code]
- property iTunes : "com.apple.Music"
-
if application id iTunes is running then
- display dialog "Music is running"
- end if
The other new iTunes successor apps—Podcasts and TV—don’t seem to be scriptable via AppleScript.
NSSound.currentTime
Within other scripting languages, there’ve been more interesting, if obscure, changes. In 42 Astounding Scripts I have a Swift script for playing alert sounds.
[toggle code]
- //wait until the sound is done before going on
-
while sound.currentTime < sound.duration {
- usleep(100000)
- }
Waiting is necessary because if the script exits before the sound is finished, the sound will stop. The obvious method of checking if the sound is still playing—NSSound.isPlaying
—doesn’t work for alert sounds as it does for audio files. It remains true until calling NSSound.stop()
, which makes it useless for deciding when to call NSSound.stop()
.
But in Catalina, the currentTime
property no longer counts up to the duration
property and stays there. It counts up to the duration
property and then drops to zero, which means that unless you’re very lucky and hit the top of the while loop exactly when the two properties are equal, that loop will never end.
The problem was compounded by the fact that the currentTime
property is not immediately updated after starting the sound. When I changed the while condition to NSSound.currentTime > 0
the loop never executed. The first time through, currentTime
was zero.
This necessitated using one of my favorite constructs, but one that is, sadly, almost never necessary: a loop with its condition at the end.
[toggle code]
- //wait until the sound is done before going on
-
repeat {
- usleep(100000)
- } while sound.currentTime > 0
The contents of the loop are executed, and only after the first execution is the condition checked.
This loop form is so rarely needed that some scripting languages don’t even include it. Python doesn’t have one.1 AppleScript doesn’t have one.
But Swift does, and this is the perfect time to use it. It ensures that the sleep is executed at least once before checking whether currentTime
has dropped back to zero.
osascript and Contacts
More serious was that in a JavaScript osascript that calls the Contacts app, the data started coming back with strange characters around some field titles:
- $ contacts wayne
- full name: Bruce Wayne
- birthday: 2/19/1982
- _$!<Home>!$_ address: 1007 Mountain Drive, Gotham, NJ, 12345, USA
- _$!<Work>!$_ address: 380 S. San Rafael Ave, Pasadena, CA, 91105, USA
- _$!<Mobile>!$_ phone: 735-185-7301
As far as I can tell, _$!<LABEL>!$_
is the actual text returned by Contacts.2 I fixed it by creating a getLabel
function that removes those characters if they appear.
- var labelText = record.label() + " " + fieldName;
and
- var addressLabel = address.label() + " address"
become:
- var labelText = getLabel(record) + " " + fieldName;
and
- var addressLabel = getLabel(address) + " address"
The function is:
[toggle code]
- //remove extraneous text from labels
-
function getLabel(field) {
- var label = field.label()
-
if (label.substring(0,4) == '_$!<') {
- label = label.substring(4, label.length-4)
- }
- return label
- }
This is obviously not satisfactory, but it does the job for now.
zsh
Catalina changes the default shell for Terminal. It uses zsh instead of bash. The two shells are similar. I was able to alter most of my bash scripts simply by changing the shebang from bash to zsh.
The main difference is in using history and tab completion. I added case insensitive tab completion while writing 42 Astounding Scripts thinking it would be helpful. But the benefits have been minimal, and in fact it’s annoyed me more often than it’s been useful. Since making zsh case insensitive is more complicated than making bash case insensitive, I’ve removed that from the book.
If you want case insensitive tab completion and you want to use zsh, you can do a web search on zsh case insensitive. If you don’t particularly need zsh, you can switch to bash, where you can add bind "set completion-ignore-case on”
to your .bash_profile.
You can change your default shell (the one that you use when you open a new Terminal window) using:
- chsh -s /bin/zsh
or
- chsh -s /bin/bash
The first switches your account to use zsh in Terminal (the default for Catalina or later) and the second switches to bash (the default for Mojave or earlier). Your default doesn’t change if you upgrade. So if you’ve been using Mojave you’ll still have bash as your shell in Catalina, unless you change it yourself.
As I wrote in the book, I don’t really care which shell I use, so I switched to zsh. I did this mainly because that’s the shell that new readers of 42 Astounding Scripts will have. If you’ve been using the Terminal since before Catalina and you’re happy with bash I don’t see any reason to change.
The main difference is that the default prompt ends in % rather than in $.
If you want to get rid of duplicates in your shell’s history, zsh uses setopt instead of export. If you want to combine your histories from multiple open Terminal windows, zsh uses setopt instead of shopt.
- export HISTCONTROL=erasedups
- shopt -s histappend
becomes:
- setopt HIST_IGNORE_ALL_DUPS
- setopt APPEND_HISTORY
Perhaps most annoyingly, where in bash you typed “history” to get all previous commands, in zsh you must type “history 1” or you’ll get only the most recent commands. I grep ancient history far more often than I look at recent history.
cron and launchd
The deprecation of cron continues; while I recommend that you use a launchd app such as Lingon X instead of cron, it is advice I don’t take myself. Adding a script to cron remains much easier than adding it to launchd.
In Catalina, cron needs permission to run programs. The easiest way to do this is to give cron “Full Disk Access” in your Security & Privacy System Preferences.
- In the Terminal, type
open /usr/sbin
to open the folder that contains cron. - Under the Apple Menu, open System Preferences and go into Security & Privacy.
- In the Privacy tab, look in the list on the left for Full Disk Access and choose it.
- Click the “+” button and drag cron from the /usr/sbin folder into the File Chooser.
- Click the Open button.
Cron will now be listed in the files with full disk access, with a checkmark next to it.
This is not something I particularly recommend. But if you’re using cron and don’t want to start using launchd, it appears to be necessary.
If you decide to use launchd, you’ll probably want to avoid calling scripts directly. That requires giving the scripting language—Perl, for example—full disk access. Instead, make an AppleScript or Automator app. The first time the app runs, it will ask for permission to access your disk. So set it to run in the next minute, give it permission, and then change it to run when you really want it to run.
iCalBuddy
The indispensable iCalBuddy doesn’t work in Catalina. But there’s an update by David Kaluta that does.
Python would have trouble implementing conditional-at-the-end loops due to the format of Python blocks. They can’t have conditions at the end of a block because blocks are defined by indentation following a block start. But if the need was serious, they would have found a way.
↑With LABEL replaced by the label name, of course.
↑
- 42 Astounding Scripts, Catalina edition
- I’ve updated 42 Astounding Scripts for Catalina, and added “one more thing”.
- 42 Astoundingly Useful Scripts and Automations for the Macintosh
- MacOS uses Perl, Python, AppleScript, and Automator and you can write scripts in all of these. Build a talking alarm. Roll dice. Preflight your social media comments. Play music and create ASCII art. Get your retro on and bring your Macintosh into the world of tomorrow with 42 Astoundingly Useful Scripts and Automations for the Macintosh!
- iCal for Catalina: David Kaluta
- “Command-line utility for printing events and tasks from the macOS calendar database. (NOW 64-BIT!)”
- Lingon: Peter Borg
- “An easy to use yet powerful app to run things automatically.”
More Astounding Scripts updates
- Catalina: iTunes Library XML
- What does Catalina mean for 42 Astounding Scripts?
- Random colors in your ASCII art
- One of the great things about writing your own scripts is that when you need new functionality, you can add it. I needed random colors in a single-character ASCII art image. It was easy to add to the asciiArt script. Here’s how.
- Avoiding lockFocus when drawing images in Swift on macOS
- Apple’s recommendation is to avoid lockFocus if you’re not creating images directly for the screen. Here are some examples from my own Swift scripts. You can use this to draw text into an image, and to resize images.
- 42 Astounding Scripts, Catalina edition
- I’ve updated 42 Astounding Scripts for Catalina, and added “one more thing”.
- Big Sur and Astounding Scripts
- Big Sur does not appear to need any changes to any of the scripts in the book.
- Two more pages with the topic Astounding Scripts updates, and other related pages
More cron
- Bluetooth battery early warning system
- Use GeekTool, or crontab or launchd and notifications, to know when your bluetooth batteries need recharging.
More iTunes
- Catalina: iTunes Library XML
- What does Catalina mean for 42 Astounding Scripts?
- A present for Palm
- Palm needs a little help understanding XML.
- Getting the selected playlist in iTunes
- It took a while to figure out how to get iTunes’s selected playlist as opposed to the current playlist in AppleScript.
- Cleaning iTunes track information
- Python and appscript make it easy to modify iTunes track information in batches—if you’re willing to get your hands dirty on the Mac OS X command line.
- Play this song backwards in iTunes
- Using AppleScript and Quicktime, you can play any song backwards. Find out what Fred Schneider was saying on “Detour Thru Your Mind”.
- Five more pages with the topic iTunes, and other related pages
More JavaScript
- Why I still use RSS
- I still use RSS because connections regularly fail, especially to Twitter.
- Converting HTML lists to text on the fly
- Switching to using lists to display code was a compromise between readability and copyability. Now that I’m creating the lists on the fly, it is easy to add features that reduce the side effects of this compromise.
- JavaScript for Beginners revised
- I’ve completely revised my JavaScript for Beginners tutorials to be more in tune with modern JavaScript, and to provide more useful examples in general.
- JavaScript for Beginners update
- The JavaScript tutorial has been updated by introducing loops earlier, and in the first section.
- Webmaster in a Nutshell
- Without doubt the best reference work for webmasters that you’ll find. It contains the “reference” part of most of O’Reilly’s web-relevant nutshell books. You can find references for HTML 3.2, the CGI standard, JavaScript, Cascading Style Sheets, PHP, the HTTP 1.1 protocol, and configuration statements and server-side includes for the Apache/NCSA webservers.
- One more page with the topic JavaScript, and other related pages
More macOS
- Adding parenthetical asides to photograph titles on macOS
- Use Applescript to append a parenthetical to the titles of all selected photographs in Photos on macOS.
More Swift
- Creating searchable PDFs in Ventura
- My searchablePDF script’s behavior changed strangely after upgrading to Ventura. All of the pages are generated at extremely low quality. This can be fixed by generating a JPEG representation before generating the PDF pages.
- Create searchable PDFs in Swift
- This Swift script will take a series of image scans, OCR them, and turn them into a PDF file with a simple table of contents and searchable content—with the original images as the visually readable content.
- ISBN (128) Barcode generator for macOS
- Building on the QR code generator, this script uses CIFilter to generate a Code 128 barcode for encoding ISBNs on book covers.
- Place a QR code over an image in macOS
- It's simple in Swift to create a QR code and place it over an image from your Photos or from any file on your computer.
- Caption this! Add captions to image files
- Need a quick caption for an image? This command-line script uses Swift to tack a caption above, below, or right on top of, any image macOS understands.
- Three more pages with the topic Swift, and other related pages