25.2.
Osascript
Three command-line tools are provided for accessing AppleScript from Unix
osalang
,
osacompile
, and
osascript
of which
osascript
is the most important. So let's talk about the other two first.
osalang
lists the scripting
components
present on your machine (see "The
Open
Scripting Architecture" in Chapter 3):
%
osalang -l
ascr appl cgxervdh AppleScript
scpt appl cgxervdh Generic Scripting System
If you have other OSA components installed, they will also appear. (For example, if you use Script Debugger, you'll see AppleScript Debugger X and JavaScript.) The two
four-letter
codes identifying each component are used by OSA programmers, but typically won't arise in the context of your AppleScript experience. Then comes a series of flags describing the capabilities of this scripting component (see the
osalang
man page for their meanings). Finally, we have the
name
of the component. The "Generic Scripting System" is the general front end to the OSA (what Chapter 3 calls the generic system component or GSC); "AppleScript" is the AppleScript scripting component in particular. You can use either of these two terms as a language
specifier
in calling the other two command-line tools, but their effect will be identical, because the GSC will treat AppleScript as the default component. In general, unless you are using other OSA scripting components, you'll have no need for
osalang
.
osacompile
takes as argument a text file, or some text provided on the command line, and generates a compiled script file or applet. For example:
%
cat > textfile.txt
tell app "Finder"
display dialog "Hello, world!"
end
^D
%
osacompile -o compiledfile.scpt textfile.txt
The result is a compiled script file
compiledfile.scpt
that can be opened in Script Editor, executed by a script runner, and so forth. You can avoid the intermediate text file by including the script text as part of the
osacompile
command; the ensuing discussion of
osascript
shows how. The extension on the filename supplied in the
-o
parameter determines the type of file that's created; the
.scptd
extension makes a script bundle, the
.app
extension makes an applet bundle, and
otherwise
you get a compiled script file. Further switches let you determine which fork the compiled script bytecode is saved into (the default is the resource fork; see "Compiled Script File Formats" in Chapter 3) and the characteristics of a created applet (see "Applet Options" in Chapter 27). Consult the
osacompile
man pages for more information.
The
osascript
command executes a compiled script file, a text file, or text provided from the command line. This command is the real key to bridging the gap between Unix and AppleScript from the Unix side.
Here's how to execute a text file or a compiled script file (these are the same files we made earlier in connection with
osacompile
):
%
osascript textfile.txt
%
osascript compiledfile.scpt
You can enter the script text manually after the
osascript
command:
%
osascript
tell app "Finder"
display dialog "Hello, world!"
end
^D
To include the script text on the command line, use the
-e
switch. The shell's usual quotational hoops will have to be jumped through; the
bash
shell helps by permitting a literal string to be entered in ANSI-C form:
%
osascript -e $'tell app "Finder"\rdisplay dialog "Hello, world!"\rend'
Another approach to entering a
multiple-line
script is to repeat the
-e
switch multiple times. For example:
%
osascript -e 'tell app "Finder"' -e 'display dialog "Hello, world!"' -e 'end'
The result of
osascript
is formatted differently depending on whether you supply the
-ss
flag. If you include it, delimiters are used, as in the Script Editor, to show the nature of the result. If you omit it, a list of strings is flattened into a series of comma-delimited tokens:
%
osascript -ss -e 'tell app "Finder" to get name of every disk'
{"feathers", "gromit", "Network"}
%
osascript -e 'tell app "Finder" to get name of every disk'
feathers, gromit, Network
The same list flattening extends to a deeper level:
%
osascript -e '{"Mannie", {"Moe"}}'
Mannie, Moe
Such a flattened, unquoted list does have its uses,
especially
in conjunction with other Unix tools. In this example (suggested to me by Chris Nebel), I find all persons who appear more than once in my Address Book:
%
osascript -e 'tell app "Address Book" to get name of every person'
tr, "\n" sort -bf -k2 uniq -d
Mark Anbinder
Mary Byrd
Jeff Carlson
[and so on]
The line-break character represented by the keyword
return
is a Macintosh line-break character (
\r
), which can confuse the display in a Unix context. This is purely a cosmetic issue. For example (in the Terminal):
%
osascript -e 'set pep to "Manny" & return & "Moe"'
Moeny
%
osascript -e 'set pep to "Manny" & (ASCII character 10) & "Moe"'
Manny
Moe
What
happened
in the first reply is that
"Moe"
overprinted
"Manny"
. If you expect that Macintosh line breaks will appear in the output, you can pipe it through
TR
:
%
osascript -e 'set pep to "Manny" & return & "Moe"' tr "\r" "\n"
Manny
Moe
Do not use any scripting addition commands that put up a
user
interface, such as
display dialog
, directly from within
osascript
. The problem is that
osascript
is not an application context, so it has no provision for user interactivity. That's why we
targeted
the Finder in the earlier examples involving
display dialog
. In Tiger, an attempt to use an interactive scripting addition command will be blocked coherently with a nice error message ("No user interaction allowed"); in earlier systems, however, the dialog would appear, but you couldn't click the
buttons
to dismiss it, and the only escape was to kill the
osascript
process.
Arguments after the script on the command line with
osascript
are treated as a list of strings to be passed to the script's run handler (see "The Run Handler" in Chapter 9); this feature is new in Tiger. For example:
%
cat > textfile.txt
on run what
set total to 0
repeat with anItem in what
set total to total + anItem
end
return total
end
^D
%
osascript textfile.txt 1 2 3
6
You are most likely to use
osascript
not directly from the command line, but from within a shell script written in a language such as Perl. In this situation it's rather easy to tie oneself in knots escaping
characters
when constructing a string intended for
osascript
, because two environments, the shell script language and the shell, are going to munge this string before it
reaches
AppleScript. This line of Perl shows what I mean:
$s = `osascript -e "tell app \"Finder\" to get name of every disk"`;
The Perl backtick operator hands its contents over to the shell for execution, but first there's a round of variable interpolation within Perl; during that round, the escaped backslashes are mutated into single backslashes, and these single backslashes correctly escape the double quotes around
"Finder"
in the string that the shell receives as the argument to
osascript
.
One solution is to single-quote the string, which avoids the round of interpolation. This example is a Perl script that single-quotes everything in sight, and also illustrates how easily Perl lets you create a literal string representing multiline AppleScript code:
$howdy = 'tell app "Finder"
display dialog "howdy"
end';
`osascript -e '$howdy'`;
An even better approach is to use a "here document ," which is supported by most scripting languages. This eliminates worries about escaping characters while letting you construct the AppleScript code dynamically through variable interpolation. To
illustrate
, here's a rather silly Perl program intended to be run in the Terminal; it asks the user for the number of a disk and then fetches the name of that disk:
#!/usr/bin/perl
$s = <<"END_S";
tell application "Finder"
count disks
end tell
END_S
chomp ($numDisks = `osascript -ss -e '$s'`);
print "You have $numDisks disks.\n",
"Which one would you like to know the name of?\n",
"Type a number between 1 and $numDisks: ";
while (<>) {
chomp;
last if $_ < 1 $_ > $numDisks;
$ss = <<"END_SS";
tell application "Finder"
get name of disk $_
end tell
END_SS
print `osascript -ss -e '$ss'`,
"Type a number between 1 and $numDisks: ";
}
Observe that the result of
osascript
has an extra return character appended to it, which has to be chomped if that isn't what we wanted. Here's the game in action,
played
in the Terminal:
%
./disker.pl
You have 3 disks.
Which one would you like to know the name of?
Type a number between 1 and 3:
1
"feathers"
Type a number between 1 and 3:
2
"gromit"
Type a number between 1 and 3:
4
Here's a serious real-life example of
osascript
in action. It started out as a Ruby script written to construct a histogram of a text file, showing the 30 most frequently used words in the file. When the question arose of how to store the results, it seemed a good idea to put them into a Microsoft Excel spreadsheet; the obvious way to transfer the data from Ruby to Excel was AppleScript. At that point, it also seemed a good idea to have Excel chart the the results (as shown in Figure 25-1).
Here's the script:
#!/usr/bin/ruby
class Histogram
def initialize
@tally = Hash.new(0)
end
def analyze(s)
s.split.each do word
myword = word.downcase.gsub(/[^a-z09]/, "")
@tally[myword] = @tally[myword] + 1 if !myword.empty?
end
@tally.sort { x,y y[1]<=>x[1] }
end
end
analysis = Histogram.new.analyze(File.new(ARGV[0]).read)
counter = 1
oneline = ""
analysis[0..29].each do entry
oneline = oneline + "set the value of cell 1 of " +
"row #{counter.to_s} to \"#{entry[0]}\"\n"
oneline = oneline + "set the value of cell 2 of " +
"row #{counter.to_s} to #{entry[1].to_s}\n"
counter = counter + 1
end
script = <<DONE
tell application "Microsoft Excel"
activate
tell worksheet 1
#{oneline}
end tell
select range "A1:B30"
set chartSheet to make new chart sheet at beginning of active workbook
set c to active chart
tell c
set chart type to bar clustered
set has title to true
set caption of its chart title to "30 Most Frequent Words"
set has legend to false
apply data labels type data labels show label without legend key
end tell
repeat with axt in {category axis, series axis, value axis}
repeat with ax in {primary axis, secondary axis}
try
tell (get axis c axis type axt which axis ax)
set has major gridlines to false
set has minor gridlines to false
set has title to false
end tell
set has axis c axis type axt axis group ax without axis exists
end try
end repeat
end repeat
end tell
DONE
`osascript -ss -e '#{script}'`
The script is called from the Terminal command line like this:
%
histogram.rb somefile.txt
|