LearningAppleScript
I'm not really keen on learning AppleScript in any depth, it's just that on rare occasions I find myself in a position where an AppleScript program would help me to perform a certain job, and I keep forgetting how the darn thing works. So here are is another learning page that I quickly threw together from reading a couple of sources, most notably the Wikipedia page.
References
- AppleScript Language Guide. The official Apple documentation. The document has the following prominent disclaimer at the top: "This document is no longer being updated." I have been unable to find newer versions of the document, so I wonder whether Apple is discontinuing AppleScript.
- Wikipedia
Glossary
- AEOM
- AppleEvent Object Model. Specifies the objects any particular application "knows".
Tools
- The Script Editor app that comes with macOS (located in /Applications/Utilities)
AppleScript as a Unix executable
Create a new file with this shebang:
#!/usr/bin/osascript
Give the file the executable bit:
chmod +x /path/to/script
Script structuring
Comments
-- Comment until end of line # Comment until end of line (* A multi-line comment *)
Multi-line statements
The character ¬
, produced by typing Option+Enter in the Script Editor, makes the current statement continue to the next line, just like the backslash character does in a shell script:
log ¬ "number of elements in aListVariable = " & ¬ (count of aListVariable)
Variables and constants
Variables
Variables do not need to be declared. They also do not have a type, meaning that the same variable can first hold a value of one type, then set later to hold a value of a different type.
Variables are assigned to either with the set
or the copy
keyword. Examples:
set aVariable to "foo" set {aVariable, anotherVariable} to aThirdVariable copy 1.2345 to aVariable
Constants
TODO: How do I define a constant?
Useful built-in constants:
true
,false
missing value
. Can be used as a placeholder for missing or uninitialized information, similar tonull
in other programming langues. However, a variable that has not been used before does not exist and does not contain themissing value
constant - you have to explictly initialize a variable with the constant. To test for the constant, use this:if aVariable is equal to missing value then [...]
.pi
result
. When a statement is executed, AppleScript stores the resulting value, if any, in the predefined propertyresult
.space
,tab
,return
,linefeed
,quote
Control statements
Conditionals
if <condition> then [...] if <condition> then [...] else if <condition> then [...] else [...] end if
Loops
AppleScript does not have an equivalent of a continue
statement.
# Endless loop repeat [...] # Terminate the loop. "exit repeat" can be simplified to just "exit". if <condition> then exit repeat end repeat # Pre-determined number of iterations repeat 42 times [...] end repeat # Iterate over a list repeat with aLoopVariable in aListVariable [...] end repeat # Conditional loops repeat while <condition> [...] end repeat repeat until <condition> [...] end repeat # for-loop repeat with aVariable from 0 to 42 [...] end repeat repeat with aVariable from 0 to 42 by 3 [...] end repeat
Data types
Booleans
AppleScript knows the usual boolean values true
and false
.
Booleans have class boolean
.
Numbers
AppleScript has integer and real number data types. Examples:
set aVariable to 1 set aVariable to -42 set aVariable to 5.678 set aVariable to 5.678E+10
Numbers have either class integer
or real
.
Strings
Strings consist of Unicode characters. Literals are enclosed in doublequotes. Strings are concatenated with the &
operator. Examples:
set aVariable to "foo" set aVariable to "foo" & "bar" set aVariable to "the answer is " & 42 # if left operand is a string, the right operand is coerced into a string
Strings have class text
.
Lists
Lists have class list
.
Basic literals:
set aListVariable to {} set aListVariable to {1, 2, "3", 4, 5.6789} set aListVariable to {1, 2, {3, 4}, 5, 6}
Changing a list:
set beginning of aListVariable to "foo" # add to the beginning set end of aListVariable to "foo" # add to the end
Accessing items:
set aVariable to item 42 of aListVariable
Records
A record is an unordered collection of key/value pairs where the key is a string-like label and the value can have any type. In other programming languages similar concepts are "associative arrays" or dictionaries where the key has the type string.
Note that in the following example the label is not enclosed in doublequotes to form a string.
set aRecord to { foo: "bar", baz: 1.234 }
Records have class record
. Records can be concatenated using the &
operator.
Operators
A selection of the most useful operators:
and
,or
,not
&
. Concatenation of strings, records or lists. For records, the properties in the left-hand operand take precedence. Operands that are neither strings, records nor lists are placed into a temporary list with that operand as the single item, then concatenation occurs.=
,is equal
,equals
,equal to
,is equal to
. Equality. Operands of different type are not coerced but generally treated as not equal. A certain leeway is given to scalar operands such as booleans, integers and reals. For lists the order of items must match. For records the order in which properties appear is not relevant.≠
,is not equal
,is not equal to
,is not
,isn't
,isn't equal
,isn't equal to
,doesn't
,does not equal
. Inequality.>
,>=
,<
,<=
, and similar natural language constructs as shown above for equality/inequality - just try it out.starts with
,ends with
, and other similar forms. Used to compare the beginning/end of a string or list.contains
,is in
,does not contain
,is not in
, and other similar forms. Used to check for containment/non-containment within a string, list or record.mod
,div
. Modulus and integral division.^
. Exponentiation.as
. Coerces its left-hand operand to the class specified on the right-hand side.
Coercion
The as
operator can be used to explicitly coerce a value from one type to another. Example:
set aVariable to 42 as text
Implicit coercion occurs when an object is of a different class than was expected.
Locale:
- When coercing text strings to values of class
integer
,number
, orreal
, or vice versa, AppleScript uses the current Numbers settings in the Formats pane in International preferences to determine what separators to use in the string. - When coercing strings to values of class
date
or vice versa, AppleScript uses the current Dates settings in the Formats pane.
Functions (aka handlers)
AppleScript calls a function a handler. The following examples show the difference between functions with positional and labelled parameters. Not shown here:
- There are additional ways to define functions with labelled parameters, with the goal to be able to write code that looks like natural language, but these are too weird/exotic to write down here - refer to Apple's documentation for details.
- There are also "functions with interleaved parameters", which apparently are useful for bridging into Objective-C since they naturally resemble Objective-C syntax. This is also something not discussed here.
- Last but not least, a script can have special handlers called "run handlers" (executed when the script is launched), "open handlers" (executed when the script is launched with the "open" command), "idle handlers" (periodically executed when the script is a "stay-open application" and is not executing any other code) and "quit handlers" (executed when the script is a "stay-open application" and is terminated by the user). These are useful for writing AppleScripts that can be considered applications in their own right. I'm not showing these things here because they are out of my scope.
# Declare a function with positional parameters on aFunctionWithPositionalParameters(parameter1, parameter2, [...]) [...] # Exits the handler and returns the specified value. # Specifying a value is optional. TODO: What does this return? # If there is no return statement, AppleScript returns the # value returned by the last statement. return <expression> end aFunctionWithPositionalParameters # Declare a function with labelled parameters on aFunctionWithLabelledParameters given label1:param1, label2:param2, [...] [...] end aFunctionWithLabelledParameters # Invoke the functions aFunctionWithPositionalParameters("foo", 1.2345) aFunctionWithLabelledParameters given label2:"bar", label1:"foo" # To invoke a function from within a "tell" block, you must use # the "my" or "of me" keywords tell application "foo" my aFunction() aFunction() of me end tell
Error handling
try [...] on error [...] end try
AppleEvent Object Model
Summary
AppleEvent Object Model = AEOM.
The AEOM allows an application to define human-readable terms for two types of things, so that scripts can be written similarly to natural language:
- Objects. Examples: document, paragraph, photo, album
- Actions. Examples: cut, close, select, start slideshow
AEOM in general also defines ways to refer to properties of objects in natural language. For instance, in iPhoto one can refer to the "fourth photo of the events album".
Application dictionaries
The objects and actions supported by an application together make up a dictionary that can be used to script the application. To view the dictionary of an application in Script Editor:
- Select "File > Open Dictionary"
- Select the application that you want to script
Keyword "tell"
The keyword tell
is used to send an AppleEvent to an object. Less technically, one could say that we want an object to perform a given action. Example:
tell <object> "foo" to <action>
When an object should execute several actions in a row, a tell
block can be used as shown in the following example. Within the block, the default target of events/actions is changed and does not need to be specified.
tell <object> "foo" <action> end tell
Keywords "it" and "me"
The keyword it
refers to default target of events/actions. This means that a tell
block change the value of this keyword.
The keyword me
refers to the AppleScript that is currently running. Outside of any tell
block, the two keywords refer to the same thing.
The keyword my
is a synonym for of me
, and the keyword its
is a synonym for of it
.
Object hierarchy
At the top of an application's object hierarchy is the application itself. These are all the same:
tell application "foo" to quit tell application "foo" quit end tell # Special notation that is available only for events in the "Core Suite". # These are the events: activate, open, reopen, close, print, and quit. quit application "foo"
Objects further down in the hierarchy can be used either via nested tell
blocks, or (as Wikipedia puts it) using nested prepositional phrases. Some of the following examples are stolen from Wikipedia.
tell application "QuarkXPress" tell document 1 tell page 2 tell text box 1 set word 5 to "Apple" end tell end tell end tell end tell pixel 7 of row 3 of TIFF image "my bitmap" # Similarly you can use the 's notation instead of the "of" keyword: tell application "foo" set aVariable to first window's name end tell
Recipes
Diagnostics
Use the log
keyword to send output to the logging window of Script Editor. This is a useful diagnostics/debugging technique
Example:
log "it currently is " & (name of it)
Here's a reusable handler that outputs a log message with a time stamp:
# -------------------------------------------------------------------------------- # Logs the specified message using the "log" statement. Prepends the message with # a time stamp so that it is possible to see how much time has elapsed since the # previous message. # -------------------------------------------------------------------------------- on logMessage(message) set currentDate to current date log ¬ "" & (short date string of currentDate) ¬ & ¬ " " & (time string of currentDate) ¬ & ¬ " " & message end logMessage
Simulate keystrokes
This StackOverflow answer has many examples and also lists many key codes for non-printable characters. The SO answer mentions that the key codes are listed in a file named Events.h
, but for the moment I don't know where this file can be found.
Here are some of the most importants examples:
activate application "foo" tell application "System Events" to keystroke "a" tell application "System Events" to keystroke "a" using command down tell application "System Events" to keystroke "a" using {control down, command down} tell application "System Events" to key code 48
Dialogs, Alerts, Selections
All examples stolen from Wikipedia:
# Dialog set dialogReply to display dialog "Dialog Text" ¬ default answer "Text Answer" ¬ hidden answer false ¬ buttons {"Skip", "Okay", "Cancel"} ¬ default button "Okay" ¬ cancel button "Skip" ¬ with title "Dialog Window Title" ¬ with icon note ¬ giving up after 15 # Alert set resultAlertReply to display alert "Alert Text" ¬ as warning ¬ buttons {"Skip", "Okay", "Cancel"} ¬ default button 2 ¬ cancel button 1 ¬ giving up after 2 # Choose from list set chosenListItem to choose from list {"A", "B", "3"} ¬ with title "List Title" ¬ with prompt "Prompt Text" ¬ default items "B" ¬ OK button name "Looks Good!" ¬ cancel button name "Nope, try again" ¬ multiple selections allowed false ¬ with empty selection allowed # Get result from an interaction method (regardless of whether # it was a dialog, alert or selection display alert "Hello, world!" buttons {"Rudely decline", "Happily accept"} set theAnswer to button returned of the result if theAnswer is "Happily accept" then beep 5 else say "Piffle!" end if
Working with files and folders
Basics:
- The classes
file
andPOSIX file
are references to files and folders. The difference is thatfile
uses HFS paths andPOSIX file
uses POSIX paths. - The class
alias
can also be used, but the file/folder must exist. - The advantage of an
alias
is that it tracks the file/folder even when it is renamed or moved to a new location, whereasfile
andPOSIX file
are a reference by path - There are AppleScript built-in commands that can be used to open files for reading or writing. Currently I'm not writing anything about these here.
- Other filesystem manipulations are generally done using the Finder application
Some code snippets:
createFolder("foo", "/path/to/parent-folder") # -------------------------------------------------------------------------------- # Creates a new folder with the specified name in the specified parent folder. # The parent folder path must specified as a POSIX path. The parent folder must # exist. Spaces or other special characters must not be escaped. Shell'isms such # as the "~" character (which you might expect to refer to the user's home # directory) do not work. # -------------------------------------------------------------------------------- on createFolder(folderName, parentFolderPosixPath) # This check exists purely so that we can log a useful message. # Without the log message, the script aborts with a useless # error message. if not my pathExists(parentFolderPosixPath) then logMessage("Parent folder does not exist: " & parentFolderPosixPath) end if # Must appear outside of the tell block, otherwise there's an error. # I don't know why, though... set parentFolder to POSIX file parentFolderPosixPath tell application "Finder" # If the folder already exists the script aborts with a useful # error message make new folder at parentFolder with properties {name:folderName} end tell end createFolder # -------------------------------------------------------------------------------- # Returns true if the specified POSIX path exists. Returns false if the specified # POSIX path does not exist. # -------------------------------------------------------------------------------- on pathExists(posixPath) tell application "System Events" try if exists folder posixPath then return true else return false end if on error # The only reason why we would get an error is because # the specified path exists but is not a folder, which causes an # error when we try to create a "folder" object from # the path. Since we don't care about the path's type, # we happily return true here. return true end try end tell end pathExists
Notes on scriptable applications
iPhoto
Events are effectively inaccessible via AppleScript. Some notes on events:
- If the user selects an event in the events album, then the selection that can be obtained via AppleScript is a list of photos - not an event as one might expect!
- The
children
property of the events album is an empty list - The first photo in the events album is not necessarily in the top-left event. Probably the first photo is the oldest photo.
- Programmatically selecting a photo in the events album via AppleScript causes the event that contains the photo to become selected in the UI
Some code snippets:
tell application "iPhoto" # Display nothing set eventsAlbum to get events album set eventsAlbumChildren to children of eventsAlbum repeat with anEventsAlbumChild in eventsAlbumChildren set childName to name of anEventsAlbumChild display dialog childName end repeat # Includes the events album and some of the other special albums set photoLibraryAlbum to get photo library album set photoLibraryAlbumChildren to children of photoLibraryAlbum display dialog (name of photoLibraryAlbum) & ", count = " & (count of photoLibraryAlbumChildren) # Unsuccessful attempt to understand what "local root albums" are set localRootAlbums to get local root albums log "number of localRootAlbums = " & (count of localRootAlbums) repeat with localRootAlbum in localRootAlbums log "name of localRootAlbum = " & name of localRootAlbum end repeat set allAlbums to get every album log "number of allAlbums = " & (count of allAlbums) set theSelection to get selection log "class of theSelection = " & class of item 1 of theSelection log "number of items in theSelection = " & (count of theSelection) end tell # Play around with changing the selection via simulating keystrokes activate application "iPhoto" repeat 10 times tell application "System Events" to key code 124 # RightArrow tell application "System Events" to keystroke code 124 # RightArrow tell application "iPhoto" set theSelection to get selection log "number of items in the selection = " & (count of theSelection) log "class of item 1 of theSelection = " & class of item 1 of theSelection end tell end repeat # Incomplete and failed attempt to simulate user keystrokes to select the # first event in the events album, regardless of where the selection # currently is. tell application "iPhoto" # Makes sure to select the events album if it is not currently selected set eventsAlbum to get events album select eventsAlbum set theSelection to get selection # The selection is always a list, so we need to examine one of its items set itemOneOfTheSelection to item 1 of theSelection if (itemOneOfTheSelection = eventsAlbum) then # If the selection still consists of the events album, then # no event is selected in the detail view activate application "iPhoto" # Send Tab key to set focus on detail view # The problem is that the focus may already be in the detail view - in that case # sending the Tab key sets the focus to the album tree on the left-hand side tell application "System Events" to key code 48 # Tab # This works! tell application "System Events" to key code 124 # send RightArrow key to select the first event else # If the selection consists of a photo, then an # event is selected in the detail view activate application "iPhoto" # Send Home key to select the first event # This does not work! Sending Home has no effect. What might possibly # work is to send ArrowUp key codes until the selection no longer changes # (in which case we are at the top row of events), then ArrowLeft key codes # until the selection no longer changes (in which case we are at the top-left # event). activate application "iPhoto" tell application "System Events" to key code 115 # Home end tell