Scripting Basics

The following notes provide a bare outline of the object-oriented programming (OOP) language known as AppleScript. For further information, see the AppleScript Language Guide (Addison-Wesley 1993), a scholarly although rather tedious tome.

The author originally found this language difficult, perhaps due to his familiarity with calculus and BASIC programming. In some areas, the coercions between different object types (such as text and date) seem inconsistent while the setting up of error handling can be very frustrating.

Here’s an embellished script that empties the Trash without leaving the current application:-

set theDlg to display dialog ¬

  "Do you really want to empty the Trash?" buttons {"No", "Yes"} ¬

  default button "Yes" with icon note -- instruction to scripting addition

if button returned of theDlg = "Yes" then

  with timeout of 1 second -- if there’s a problem script gives up after 1 second

    tell application "Finder" -- instruction to the Finder

      empty -- empties the Trash

    end tell -- three lines equivalent to ‘tell application "Finder" to empty’

  end timeout

end if

-- A demonstration script for this AppleScript guide

(* Here’s a block of text used as a comment. During debugging you can enclose

unwanted portions of script in the same manner. *)

The above example includes the following features found in most scripts:-

A simple script, which contains only one section, usually works by what is known as an implied run command. For more complex scripts, which usually incorporate subroutines, an actual run command must be used to identify the main part of the script, in the form shown below:-

on run

  * ** *** -- replace this by the lines of your script

end run

When a script is used as a droplet it must also have special subroutine called open to open those items that are dropped onto it. This should be in the form shown below. In this instance the run routine also calls the open subroutine, thereby avoid any unnecessary duplication of script.

on open (itemList)

  set itemList to itemList as list

    repeat with theItem in itemList

      * ** *** -- replace by scripting for processing each item

    end repeat

end open

on run

  * ** *** -- replace this by the lines of your script

  open (theFile)

end run

The script shown below uses an open subroutine to process items that have already been selected on the desktop or chosen via an Open dialogue. This particular example gives the name of each item.

on open (itemList)

  set itemList to itemList as list

  repeat with theItem in itemList

    * ** *** -- script for processing each item

    tell application "Finder" to display dialog (name of theItem)

  end repeat

end open

on run

  try

    tell application "Finder"

      activate -- brings Finder to the front

      set theItems to selection -- gets list of selected items

      if count of theItems = 0 then -- no desktop items are selected

        repeat

          set theItems to choose file with prompt "Choose a file:"

          tell me to open (theItems) -- makes script open chosen item

        end repeat

      else

        tell me to open (theItems) -- makes script open selected desktop items

      end if

    end tell

  end try

end run

Note the the use of a count instruction that checks how many items have been selected and the repeat loop around the choose file line, which allows several items to be selected in turn. Finally, the tell me to open statements instruct the script to open the chosen items, not the Finder. If you don’t use tell me the Finder opens the items in the normal way.

In the above example, the open subroutine includes a parameter in brackets. Other subroutines often involve several parameters, or none at all. Even if you don’t use any parameters you must still use brackets, as in mySubroutine().

Error Handling

Under normal circumstances, a failed script produces a special AppleScript error dialogue that contains an error message, sometimes accompanied by an error number, as shown below:-

Usually, clicking on the Edit button causes Script Editor to launch, revealing the error as highlighted text. As you can see, this kind of dialogue is rather ugly and the messages can be confusing, although they’re useful during debugging. They also encourage other people to tinker with your scripts.

Fortunately, AppleScript has a mechanism for using standard dialogues or removing them altogether. This is achieved by wrapping the entire script (or a selected portion of a script) inside try and end try statements, as in the following:-

try

  * ** *** -- script that can cause errors

on error errMsg number errNum

  display dialog errMsg & return & "Error Number: " & errNum¬

    buttons "OK" default button "OK" with icon caution

end try

In this case errors shown in the resultant dialogues contain both the error message and error number:-

The following example displays the message on its own:-

try

  * ** *** -- script that can cause errors

  on error errMsg

    display dialog errMsg buttons "OK" default button "OK" with icon caution

end try

as shown here:-

whilst this version shows a generic message for all errors:-

try

  * ** *** -- script that can cause errors

  on error

    display dialog "An unexpected error prevented this operation." ¬

      buttons "OK" default button "OK" with icon caution

end try

which looks like this:-

Sometimes, you may want to generate error messages that vary according to what happens, whilst leaving AppleScript free to insert its own messages. Here’s an example:-

try

    if * ** *** -- script that can cause errors

        error "It went wrong."

    end if

on error errMsg

    display dialog errMsg

end try

If a script is intended to produce observable effects you can prevent all error messages with a script of the form:-

try

  * ** *** -- script that can cause errors

end try

If your try and end try statements enclose part of a script you can force it to quit by using something like:-

try

  * ** *** -- script that can cause errors

  on error

    display dialog "An unexpected error prevented this operation." ¬

      buttons "OK" default button "OK" with icon caution

    error number -128

end try

which effectively converts all errors into error number -128, the user cancelled error. The following is similar, but doesn’t produce a message:-

try

  * ** *** -- script that can cause errors

  on error errMsg number errNum

    if errNum is not -128 then ** *** -- optional instruction

    error number -128

end try

The nesting of error statements can be useful, as in this example:-

try

  * ** *** -- portion of script under ‘outer’ error handling

  try

    * ** *** -- portion of script under ‘inner’ error handling

  on error

    error number -128

  end try

  * ** *** -- portion of script under ‘outer’ error handling

  on error errMsg number errNum

    display dialog errMsg & return & "Error Number: " & errNum ¬

      buttons "OK" default button "OK" with icon caution

end try

However, there’s a problem: the outer error handling produces a ‘user cancelled’ message whenever an inner error occurs. This can be prevented by modifying the last section to something like:-

on error errMsg number errNum

  if errNum is not -128 then ¬

  display dialog errMsg & return & "Error Number: " & errNum ¬

    buttons "OK" default button "OK" with icon caution

end try

Dummy Error Numbers

In some instances you may want to generate your own error numbers, as in:-

try

  * ** *** -- portion of script under ‘outer’ error handling

  try

    * ** *** -- portion of script under ‘inner’ error handling

  on error

    error number 560

  end try

  * ** *** -- portion of script under ‘outer’ error handling

  on error errMsg number errNum

    display dialog errMsg & return & "Error Number: " & errNum ¬

      buttons "OK" default button "OK" with icon caution

end try

or as in:-

try

   if theValue = 15 then error number 1437

  on error number errNum

    display dialog "Error Number: " & errNum ¬

      buttons "OK" default button "OK" with icon caution

end try

Generally speaking, you can use any value from 500 to 10000 as an error number.

Objects

Traditional programming, such as BASIC, uses variables to store numbers and text within a program. AppleScript, in common with other OOP languages, extends this to refer to objects, such as files, disks and computer ports. In AppleScript terminology an object is known as a value, although the author prefers the familiar ‘object’ term.

AppleScript supports 14 types or classes of object, each known as an object type or value class, as listed below:-

ClassExamples
booleantrue, false
classstring, integer
constantSunday, August
data«data ics8…»
datedate "Sunday, March 14, 1999 12:30:52 pm"
integer32, 1024
list{"dog", "cat", "mouse"}
number32, 1024, 4, 16.6666
real32.0, 1024.0, 4.0, 16.6666
record{height:96, width:64}
referencefile of «script»
string"Plain text"
styled text"Text with style"
text"Plain or styled text"

Although most applications handle all classes, some prefer to use their own varieties. For example, ClarisWorks requires you to use the plain text class instead of the string class in scripts.

Coercions

Some values can be coerced into other classes. For example:-

(current date) as text

gives a result in the form of "Tuesday, February 10, 1998 4:51:49 pm".

Object Names

The name of an object mustn’t correspond to any keyword used in AppleScript, or to those used in a scripting addition or application. Most are easily avoided, although some are tricky to spot.

Object names names with capitals inside, such as theDiskSizeInKB, are often used, although some scripting additions also use these kinds of words. Names such as the_disk_size_in_KB are usually suitable, although they aren’t easy to read.

Set and Copy

Values are usually defined using set or copy, as in:-

set theText to ""

set currentFiles to {}

set n to n + 1

copy "this" to theText

copy 3 as text to numText

copy (current date) as text to dateNowTxt

The set instruction can also be used to ‘initialise’ a value at the start of a script.

A full understanding of the difference between set and copy instructions can avoid unnecessary complications. For example, in the following script:-

set listOne to {1,2,3}

set listTwo to listOne

set listOne to {4,5,6}

both listOne and listTwo end up containing {4,5,6}, since the second line has made both values equal. If you don’t want this to happen you should use copy instead, as shown below:-

set listOne to {1,2,3}

copy listOne to listTwo

set listOne to {4,5,6}

In this case listOne contains {4,5,6} and listTwo contains {1,2,3}.

The various methods of manipulating values are too numerous to describe in this guide. However a few examples are given below. The following script gets the second word of this text:-

set middleWord to word 2 of "Alpha Beta Gamma" --> "Beta"

this script obtains the length of a list:-

set theLength to length of {"A", "B", "C"} --> 3

and this selects the third item in a list:-

set theItem to item 3 of {"A", "B", "C"} --> "C"

Co-ordinates

Sometimes, it’s necessary to specify the size and position of a window or some other kind of rectangle. The size alone can be given by means of a two-item list such as {200, 100}, which specifies a rectangle of 200 × 100 pixels. Note that the width of the rectangle along the horizontal axis (the x value) is always given before the height along its vertical (the y value).

Defining both the size and position of a rectangle is slightly more difficult, requiring you to use a set of four co-ordinates. Generally speaking, these are measured from a zero point, located at the bottom left-hand corner of the screen or area under consideration. To add to the confusion, the size of a dialogue on a computer screen is normally measured from the top left-hand corner of the screen.

Co-ordinates are often given as a list in the form of {left, top, right, bottom}, which can be written as {x1, y1, x2, y2}, although the ordering of the latter isn’t entirely logical when working with co-ordinates that refer to the bottom left-hand corner. Here’s an example:-

which can be specified using co-ordinates of {6, 17, 28, 7}.

Properties

The initial contents of a value can be set using a property statement at the start of a script, such as:-

property fToWatch: " "

property oldFiles: {}

property destFolder: "NULL"

property testFlag: false

Note, however, that the contents of values defined in this way are ‘remembered’ each time the script is used. For example, if you run a script and testFlag ends up as true this value will again be used when you next run the script. Similarly, the following script tells you how many times it has run:-

property numTimesRun : 0

set numTimesRun to numTimesRun + 1

display dialog "This script has been used " & numTimesRun & " times."¬

  buttons "OK" default button 1 with icon note

Standard Instructions

The common instructions used in AppleScript can be divided into the following groups:-

Conditional Statements

These let you make decisions in a script, often involving if, else and end if commands, as in:-

if theWord = "dog" then

  set n to 2

else

  set n to 3

end if

although simple one-line statements can also be used, such as:-

if theWord = "cat" then set theFlag to true

Control statements

These determine the flow of a script, depending on a result. They often involve the use of tell, if, repeat or try statements, as well as matching end statements.

Examples of such statement have already appeared in this document. Here’s another one:-

repeat

  ** *** * -- your script goes here

  set n to n + 1

end repeat

This kind of repeat statement is rather dubious, since there’s a distinct risk of getting stuck in an endless loop. This can be fixed by adding a conditional statement inside the loop, as in:-

repeat

  ** *** * -- your script goes here

  set n to n + 1

  if n = 64 then exit repeat

end repeat

although it’s far better to to include a conditional value in the top line, as shown in these examples:-

repeat until theValue = 5

repeat while theValue is less than 5

repeat with theValue in valueList

Finally, there’s the most complex form of repeat statement:-

repeat with loopVar from lowBound to highBound by stepValue

  ** *** * -- your script goes here

end repeat

Subroutines

This portion of script performs the same action within different parts of any script, making it easy to create short and well-organised scripts. Each subroutine begins with on followed by the routine’s name and ending with a matching end statement. Here’s a routine that converts a list to text:-

on listToTxt(theList, theDelim)

  set delimLength to length of theDelim

  set theText to ""

  repeat with theItem in theList

    set theText to theText & theDelim & theItem

  end repeat

  if text 1 thru delimLength of theText is theDelim then¬

    set theText to text (delimLength + 1) thru -1 of theText

  return theText

end listToTxt

where the parameter called theList is the actual list and theDelim is the delimiter used in the list. You can ‘call up’ such a routine from within the main part of your script using a line such as:-

set theText to listToTxt({"Computer", "Disk Drive", "Modem", "Printer"}, ", ")

  --> "Computer, Disk Drive, Modem, Printer"

Arithmetical Operations

The following mathematical functions are built into AppleScript:-

OperationSymbol
Addition+
Subtraction-
Multiplication*
Division •/
The ÷ and Option-/ characters can also be used

Brackets can be used to improve the presentation of operations, although you should remember than items within an innermost parenthesis are always calculated first. This example uses a routine to find the impedance of an 10µH inductor with a DC resistance of 0.5 Ω at a frequency of 1000 Hz:-

impInduct(180 * (10 ^ -6), 0.5, 1000)

  --> 1.63

on impInduct(L, Rs, f) -- values in henrys, ohms and hertz. Rs is the DC resistance

  set Zl to Rs + (2 * pi * f * L)

  return (round (100 * Zl) ) / 100

end impInduct

Text Manipulation

AppleScript provides little in the way of built-in support for modifying text, although you can create your own routines You can also manipulate text by changing the delimiters used for separating text items, as in this example, which extracts the name of a file from its path:-

set itemPath to "Macintosh HD:Documents:My File"

try

  set oldDelims to text item delimiters

  set text item delimiters to {":"}

  set itemName to last text item of itemPath

  set text item delimiters to oldDelims

on error

  set text item delimiters to oldDelims

end try

return itemName

   --> "My File"

The try statement around the main part of this script is essential. If you don’t include this and the script fails, AppleScript continues to use the new delimiter in all subsequent scripts. For this reason, it’s better to use something like this:-

pathFileNm("Macintosh HD:Documents:My File")

   --> "My File"

on pathFileNm(theFile)

  set revFilePath to (reverse of characters 1 thru -1 of theFile) as text

  return (reverse of characters 1 thru ( (offset of ":" in revFilePath) - 1)¬

    of revFilePath) as text

end pathFileNm

which, although much more complicated, is actually shorter.

Selecting large amount of text within a script can cause memory problems, as in this example:-

if selection as text is not "" then *** ** * -- your script goes here

where the script has to process the actual text data. To avoid this, you can use:-

if data size of selection > 0 then *** ** * -- your script goes here

which instead works out the length of the text.

AppleScript often can’t perform operations on text over 32 KB, although this is usually possible if left to a scriptable application with sufficient memory. And there shouldn’t be any problem transferring data via the clipboard or other files.

Simple Application Commands

Depending on scriptability, you can send each application a range of instructions. However, if the application name entered in a script doesn’t match the real name or the script can’t locate its file, AppleScript puts up a Where is the file? dialogue, allowing you to find the file manually. Having done this and re-compiled the script you shouldn’t need to do it again.

Any application, even if it isn’t scriptable, can be launched using a script of the form:-

run application "ResEdit"

In the case of scriptable applications you can also use a line of the form:-

tell application "AppleWorks" to run

or:-

tell application "AppleWorks"

  run

end tell

Note that the tell command forces any enclosed operations to be executed from within the application’s memory space, which can cause problems, especially in Mac OS 8.5. And, in all versions of the Classic Mac OS, you can’t normally use a general command, such as display dialog, inside a tell statement unless the command is understood by the application.

You can quit any application using a command in the form of:-

quit application "ResEdit"

The launch command is slightly different, since the application doesn’t show all of the usual start-up displays when it’s launched. The actual line of script is:-

launch application "ResEdit"

The following variation is only suitable for scriptable applications, although it may cause the application to quit and relaunch if it’s already running:-

tell application "ClarisWorks"

  launch

end tell

The following kind of script usually works, but only with a scriptable application:-

tell application "ClarisWorks"

  launch

  open alias "Macintosh HD:Documents:MyTextFile"

end tell

At this stage, some people express confusion over the word alias. However, in this context the term refers to an AppleScript alias, not a standard Mac OS alias. This kind of alias refers to a specific file and keeps track of it, even in the event of it being moved or receiving a new name.

If the above script succeeds, it launches the application and the file’s window appears. Note however, that the application may not come to the front, in which case you'll need to add an extra line containing activate after the existing one containing launch. Some applications can open a list of files, as in:-

tell application "ClarisWorks"

  launch

  open {"Macintosh HD:Documents:MyTextFile", "Macintosh HD:TopFile"}

end tell

Note that this particular application doesn’t need the word alias to follow an open instruction.

Introduction to the Scriptable Finder

The Finder accepts similar commands to those used by applications. However, an open command that’s sent to the Finder shouldn’t be confused with the standard AppleScript open command. The latter opens files that are dropped onto a script in the form of a droplet.

The following example opens a file using Netscape, which is identified by a creator code of MOSS:-

tell application "Finder"

  open alias (choose file) using application file id "MOSS"

end tell

whilst this form of script opens a particular file using its default application :-

tell application "Finder" to open alias "****" -- replace asterisks by file path

while this opens a list of files:-

tell application "Finder"

  open {alias "****", alias "*****"} -- replace asterisks by file paths

end tell

The Finder can perform numerous other tasks. For example, this line of script enters the name of a Mac OS computer’s startup disk into a value called sysDisk:

tell application "Finder" to set sysDisk to name of startup disk

whilst this script moves a file identified as theFile to the path given in theFldr:

tell application "Finder" to move theFile to theFldr with replacing

The replacing instruction indicates that an existing file of the same name must be replaced. A similar script can copy a file:-

tell application "Finder" to copy theFile to theFldr with replacing

The Finder can also create new files from scratch, as in the following:-

tell application "Finder"

  make file at alias theFldr with properties ¬

    {name:fileName, creator type:"notz", file type:"TEXT"}

end tell

This example produces a Stickies text file, which is identified by creator code of notz and file type of TEXT. The new file’s filename should be contained in the value fileName.

Scripting Additions

Recent versions of the Classic Mac OS include a Standard Additions scripting addition, which lives in the Scripting Additions folder, inside the System Folder, replacing the older additions used in earlier versions of the system. Further additions can be added, as required.

The following information refers to the Standard Additions file:-

Dialogues

The most useful feature provided in Standard Additions is a dialogue box. By default, this contains up to 255 characters of text, a Cancel button and an OK button. For example, the line:-

display dialog "Do you really want to switch to Windows?"

provides the following dialogue:-

If you click the Cancel button the script invariably quits. The limitation of 255 characters can be a problem, with excessively long text becoming harmlessly truncated, as in this example:-

It’s also possible to define up to three individually named buttons, as in:-

display dialog "What is your age range?" buttons {"18-35", "36-55", "Over 55"}

which gives this dialogue:-

The default button can be selected by name or number as in:-

display dialog "Please indicate your gender."¬

  buttons {"Male", "Female", "Cancel"} default button "Cancel"

or:-

display dialog "Please indicate your gender."¬

  buttons {"Male", "Female", "Cancel"} default button 3

both of which give:-

To make the script to continue even though Cancel has been selected you must add a space before and after Cancel, as in:-

display dialog "Please indicate your gender."¬

  buttons {"Male", "Female", " Cancel "} default button 3

the spaces before and after the word ensuring that it aligns centrally in the button.

You can also create a dialogue that accepts entries, such as:-

display dialog "Enter your retirement age."¬

  buttons {"Cancel", "OK"} default answer "60" default button "OK"

which appears as:-

The result produced by such a dialogue can be extracted using:-

set theTxt to text returned of (display dialog "Enter your retirement age."¬

  buttons {"Cancel", "OK"} default answer "60" default button "OK")

The entered string will be contained in the value theTxt.

If you need to know both the string and the name of the button that was clicked you should use something like:-

set theDlg to (display dialog "Enter your retirement age."¬

  buttons {"Cancel", "OK"} default answer "60" default button "OK")

set theTxt to text returned of theDlg

set theBtn to button returned of theDlg

which can be shortened to:-

set theDlg to (display dialog "Enter your retirement age."¬

  buttons {"Cancel", "OK"} default answer "60" default button "OK")

tell theDlg to set {theTxt, theBtn} to {text returned, button returned}

Similarly, if you want the result as an integer instead of text you could use:-

display dialog "Enter your retirement age."¬

  buttons {"Cancel", "OK"} default answer "60" default button "OK"

set retAge to text returned of result as integer

In this case, AppleScript’s default value, known as result, has been used in place of the theDlg object in the previous example. Although, this is simpler it doesn’t indicate which button was clicked.

Dialogues can also be used to escape from a loop, as in this script:-

repeat

  * ** **** -- any portion of script

  display dialog "Do you want to continue?"

  if result is "No" then exit repeat

end repeat

The appearance of text entries for a dialogue can often be improved by incorporating return in the text, as in this example:-

display dialog "The current date is:" & return & (current date) as string¬

  buttons "OK" default button 1

which gives the dialogue shown below:-

The keyword tab, although acceptable in text strings, only acts as a space character in dialogues. However, the use of icons in dialogues is highly recommended, as in this an example:-

display dialog "Do you really want to erase the disk named " & ¬

  "“My Whole Life’s Work”?" with icon caution

which gives:-

You can replace caution by note for the ‘speaking man’ icon or by alert for the ‘warning hand’ icon. Similarly, other icons can be addressed by number, as in this script:-

display dialog "Do you really want to watch television?" with icon -16573

which appears as:-

The icons provided by Classic Mac OS 9.1 are shown below:-

These icons are stored as ICON or cicn resources in modern versions of the System file. You can also call up icons from within an application from within a tell statement. Applications usually have a standard icon identified by the number 128.

Other Commands

As well as providing dialogues, Standard Additions can convert values to and from their ASCII equivalents or find offsets within a string. Here’s an example:-

set theTxt to (ASCII character 65) & (ASCII character 66) --> "AB"

The same addition can also be used to handle files and folders, as in this example that uses the choose file and list folder commands to open the files in a chosen folder:-

set theFldr to ¬

  choose folder with prompt "Choose a folder to search:" -- Standard Additions instruction

set fldrList to list folder theFldr -- Standard Additions instruction

 

tell application "Finder"

  activate

  set theFldr to theFldr as text

  repeat with n from 1 to length of fldrList

    try

      open alias (theFldr & item n of fldrList)

    end try

  end repeat

end tell

There’s also a path to command, which can give the location of the front application’s file or the script’s applet file, depending on where the current script is running from:-

set thePath to path to me as text

  --> "Macintosh HD:Applications (Mac OS 9):Apple Extras:AppleScript:Script Editor"

  --> "Macintosh HD:Desktop Folder:Test Script"

As usual, the as text instruction coerces the result into a string. In the next example path to is used to locate the Temporary Items folder in the Classic Mac OS:-

set thePath to path to temporary items folder --> alias "Hard Disk:Temporary Items:"

Finally, the say instruction lets you send instructions to the MacinTalk speech synthesiser, also allowing you to increase or reduce the emphasis of particular words, as in this example:-

say "Yes, file [[ emph - ]] sharing [[ emph + ]] is on"

©Ray White 2004.