DekGenius.com
[ Team LiB ] Previous Section Next Section

23.1 Do Shell Script

The key to calling the Unix shell from your AppleScript code is the do shell script scripting addition command.

Apple provides an excellent Tech Note on this command, and your first step should be to read it (http://developer.apple.com/technotes/tn2002/tn2065.html). The direct object is a string representing the text you would type at the command-line prompt in the Terminal. But not quite, because your Terminal shell is probably bash or tcsh, whereas the default shell for do shell script is sh. Also, the default paths used by do shell script might not be the same as your own shell's default paths, so in order to specify a command you might have to provide a full pathname, such as /usr/bin/perl instead of just perl. (That's not a real example, though, since perl will probably work just fine.)

Optional parameters let you provide an administrator password. The result is whatever is returned from the command via stdout; Unix linefeed characters are converted to Mac return characters by default, but you can prevent this if you wish. If the command terminates with a nonzero result (an error), an error by the same number is thrown in your script, and you can use this number (along with the manpages for the command) to learn what went wrong.

For example, the following code requests a (decimal) number from the user and converts it to hex by means of the Unix printf command:

set theNum to text returned of (display dialog "Enter a number:" default answer "")
set s to "printf %X " & theNum
display dialog (do shell script s)

An important thing to remember about do shell script is that it does not set up an interactive shell. You give a command, you get the result, and that's all. In some cases where a tool is interactive, you can work around this problem. The tool may provide a noninteractive alternative. For instance, you might be able to pass through a file as intermediary. The following (rather silly) example illustrates the point; it converts a number to a hex string by way of bc, by writing out a small bc program file and calling bc to process it:

set theNum to text returned of (display dialog "Enter a number:" default answer "")
set t to path to temporary items
set posixT to POSIX path of t
set f to open for access file ((t as string) & "bctemp") with write permission
write "obase = 16\n" to f
write theNum & "\n" to f
write "quit\n" to f
close access f
set s to "bc --quiet " & quoted form of (posixT & "bctemp")
display dialog (do shell script s)

Similarly, if you wanted to call top, you could call it in a noninteractive form such as top -l1 and parse the result.

The hardest part of calling a Unix tool is dealing with the Unix parsing and quotation rules. To protect a string from the parsing rules, you can wrap it in single quotes. AppleScript makes this easy with the quoted form property of a string (see Section 13.4.1), and you should use it, as in the previous example. This does not absolve you from AppleScript's own rules for forming literal strings (see Table 13-1). So, in the system attribute example in Section 20.5.3, double quotation marks are entered into the literal string in escaped form, to get AppleScript to do the right thing; then the entire string is munged with quoted form to get the shell to do the right thing.

When talking to Perl, using a file as an intermediary can simplify things. There is no problem forming a short Perl script and handing it to Perl directly by means of the -e switch; but if a longer Perl script is to be formed and executed on the fly, it might make sense to write it into a file and then tell Perl to run the file. And there may be no need to form the Perl script on the fly. Perhaps your script could consist of two scripts—one in AppleScript, one in Perl.

Here's an example. There's an excellent weekly online Macintosh journal called TidBITS, and their site has a web page where you can enter words to search for and get back a page of links to past articles containing those words (see http://db.tidbits.com). We'll simulate this page, acting as a web client ourselves, with the help of curl. We know what the HTML of the results page looks like, so we've prepared a Perl script to parse it into the URLs and titles of the found articles. The Perl script expects as argument the pathname of the file containing the HTML:

$s = "";
while (<>) {
        $s .= $_;
}
$s =~ m{search results (.*)$}si;
$1 =~ m{<table(.*?)</table>}si;
@rows = ($1 =~ m{<tr(.*?)</tr>}sig);
for ($i=0;$i<$#rows;$i++) {
        ($links[$i], $titles[$i]) = 
                ($rows[$i+1] =~ m{<a href="(.*?)">(.*?)</a>}i);
}
print join "\n", @links, @titles;

Now for the AppleScript code. First we put up a dialog where the user can enter some search terms. We URL-encode these terms in a primitive way (substituting a plus sign for any spaces) and assemble the POST data for the form submission. We use curl to submit this POST data to the TidBITS server. In essence, the TidBITS server receives exactly the same HTML it would receive if a user entered the same search terms in the first field of the TidBITS Search page and pressed the Submit button.

The results come back as a page of HTML, which curl writes out to a file. We now hand this file over to our Perl script for parsing. The results come back from the Perl script, and now we have a list which is really two lists: first the URLs of the found pages, then the titles of those same pages. We put up a list showing the titles; if the user chooses one, we ask the browser to display the corresponding URL.

set t to text returned of ¬
        (display dialog "Search TidBITS for:" default answer "")
set text item delimiters to "+"
set t to (words of t) as string
set d to "'-response=TBSearch.lasso&-token.srch=TBAdv"
set d to d & "&Article+HTML=" & t
set d to d & "&Article+Author=&Article+Title=&-operator"
set d to d & "=eq&RawIssueNum=&-operator=equals&ArticleDate"
set d to d & "=&-sortField=ArticleDate&-sortOrder=descending"
set d to d & "&-maxRecords=20&-nothing=MSExplorerHack&-nothing"
set d to d & "=Start+Search' "
set u to "http://db.tidbits.com/TBSrchAdv.lasso"
set f to POSIX path of file ((path to desktop as string) & "tempTidBITS")
do shell script "curl -d " & d & " -o " & f & " " & u
set perlScript to POSIX path of ¬
        alias ((path to desktop as string) & "parseHTML.pl")
set r to do shell script "perl " & perlScript & " " & f
set L to paragraphs of r
set half to (count L) / 2
set L1 to items 1 thru half of L
set L2 to items (half + 1) thru -1 of L
set choice to (choose from list L2) as string
repeat with i from 1 to half
        if item i of L2 is choice then
                open location (item i of L1)
                exit repeat
        end if
end repeat

That code works well enough to demonstrate the principle, but one can't help feeling it's a pity that AppleScript doesn't let us present the user with some nicer interface. We'll rectify that little shortcoming in the next chapter, by building these same scripts into an AppleScript Studio application.

If you hesitate to use do shell script because you think it's scary or clumsy, stop hesitating. Unix tools are extremely valuable supplements to AppleScript's powers. I would much rather ask the shell to convert between decimal and hex, for example, than construct a handler in AppleScript alone to accomplish the same thing. I've already said (Chapter 21) that I'd rather use curl than the URL Access Scripting application. And being able to call on Perl to parse a string is sheer pleasure. The power of Unix tools is present on every Mac OS X machine; to ignore do shell script is to cut yourself off from the convenience and delight of having all this power at your command.

    [ Team LiB ] Previous Section Next Section