Previous Page
Next Page

25.2. Osascript

Three command-line tools are provided for accessing AppleScript from Unixosalang, osacompile, and osascriptof 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}'`

Figure 25-1. Excel data and chart generated by a Ruby script


The script is called from the Terminal command line like this:

% histogram.rb somefile.txt


Previous Page
Next Page