In the last post, we discussed how to run shell commands and scripts from an Apple Script environment. In this post, we will look at how we can run AppleScript commands and scripts from the shell environment.
Open Scripting Architecture
The key to running AppleScript from the shell is the
osascript command allows us to run AppleScript commands from Terminal and shell. The most common use is the user interaction commands from AppleScript, like
osascript -e 'display dialog "Hello from shell"'
-e option tells
osascript that it will get one or more lines of statements as arguments. The following argument is AppleScript code. You can have multiple
-e options which will work like multiple lines of a single AppleScript:
> osascript -e 'display dialog "Hello from shell"' -e 'button returned of result' OK
osascript prints the value of the last command to stdout. In this case, it is the label of the button clicked in the dialog. (The ‘Cancel’ button actually causes the AppleScript to abort with an error, so no label will be returned for that.)
When you have multiple lines of script, using multiple
-e statements will quickly become cumbersome and unreadable. It is easier to use a heredoc instead:
osascript <<EndOfScript display dialog "Hello from shell" return button returned of result EndOfScript
This also avoids the problem of nested quotation marks and simplifies shell variable substitution.
Shell variables and osascript
There are a few ways to pass data into
osascript from the shell.
Since the shell substitutes variables with their value before the command itself is actually executed, this works in a very straightforward manner:
computerName=$(scutil --get ComputerName) newName=$(osascript -e "text returned of (display dialog \"Enter Computer Name\" default answer \"$computerName\")") echo "New Name: $newName"
This works well, but because we want to use shell variable substitution for the
$computerName, we have to use double quotes for the statement. That means we have to escape the internal AppleScript double quotes and everything starts to look really messy. Using a heredoc, cleans the syntax up:
computerName=$(scutil --get ComputerName) newName=$(osascript <<EndOfScript display dialog "Enter Computer Name" default answer "$computerName" return text returned of result EndOfScript ) echo "New name: $newName"
I have a detailed post: Advanced Quoting in Shell Scripts.
Generally, variable substitution works well, but there are some special characters where it might choke. A user can put double quotes in the computer name. In that case, the above code will choke on the substituted string, since AppleScript believes the double quotes in the name end the string.
If you have to expect to deal with text like this, you can pass data into
osascript using environment variables, and using the AppleScript
system attribute to retrieve it:
computerName=$(scutil --get ComputerName) newName=$(COMPUTERNAME="$computerName" osascript <<EndOfScript set computerName to system attribute "COMPUTERNAME" display dialog "Enter Computer Name" default answer computerName return text returned of result EndOfScript ) echo "New name: $newName"
The shell syntax
VAR="value" command arg1 arg2...
sets the environment variable
VAR for the process
command and that command only. It is very useful.
Retrieving environment variables in AppleScript using
system attribute is generally a good tool to know.
osascript can also work as a shebang. That means you can write entire scripts in AppleScript and receive arguments from the shell. For example, this script prints the path to the front most Finder window:
#!/usr/bin/osascript tell application "Finder" if (count of windows) is 0 then set dir to (desktop as alias) else set dir to ((target of Finder window 1) as alias) end if return POSIX path of dir end tell
You can save this as a text file and set the executable bit. I usually use the
> print_finder_path.applescript /Users/armin/Documents
To access arguments passed into a script this way, you need to wrap the main code into a
#!/usr/bin/osascript on run arguments if (count of arguments) is 0 then error 2 end if return "Hello, " & (item 1 of arguments) end
You can combine this into a longer script:
macOS Privacy and osascript
When you ran the above script, you may have gotten this dialog:
If you didn’t get this dialog, you must have gotten it at an earlier time and already approved the access.
AppleEvents between applications are controlled by the macOS Privacy architecture. Without this, any process could use AppleEvents to gather all kinds of data from any process. These dialogs are easy enough to deal with when running from Terminal. But if you put your AppleScript code (or shell scripts calling AppleScript) into other apps or solutions, it could get messy quite quickly.
Mac Admins generally want their automations to run without any user interactions. You can avoid these dialogs by creating PPPC (Privacy Preferences Policy Control) profiles that are distributed from an MDM server. In this case you have to pre-approve the application that launches the script, which can sometimes also be challenge. The other option is to find solutions that avoid sending AppleEvents altogether.
I have a longer post detailing this: Avoiding AppleScript Security and Privacy Requests
osascript and root
Management scripts often run as a privileged user or root. In this case, certain features of AppleScript may behave strangely, or not at all. I generally recommend to run osascript in the user context, as detailed in this post: Running a Command as another User
AppleScript’s bad reputation may be deserved, because its syntax is strange, and often very inconsistent. Nevertheless, it has features which are hard to match with other scripting languages. You can use the strategies from this and the previous posts to combine AppleScript with Shell Scripting and other languages to get the best of both worlds.