Calling Applescript from Perl

First off, I don’t like Applescript. In fact, I hate it with a passion. You would think the Apple would learn from Cobol that natural language programming languages just suck. But no, Apple created the abomination called Applescript anyway and foisted it upon us and now sometimes you just have to suck it up and use it. Preferably from a distance with gloves on (and maybe a 10 foot pole) so that it doesn’t dirty your soul too much. But I digress.

I needed to call some Applescript from perl and was quite proud of my end result:

The usage looks like this:

osascript <<END;
 tell application "Finder"
 display dialog "Hello"
 end tell
END

So basically the Applescript sits right inside your perl program and gets launched without needing any temporary files on the disk.

How does it work?

If the perl line is incomprehensible to you, let me break it down. The inner most expression is "split(/\n/, $_[0]);". That takes the first argument to the function, breaks it apart at the line endings and puts the parts into a list. If we were to dump the result of the split using the dialog example above it would look like this:

(' tell application "Finder"', ' display dialog "Hello"', ' end tell').

Next, it uses the map function to insert “-” before every item in the list. It does that by using the fact that perl flattens arrays. The dump of the output of the map function conceptually looks like:

(('-e', ' tell application "Finder"'), ('-e', ' display dialog "Hello"'), ('-e', ' end tell'))

but really perl immediately flattens it to:

('-e', ' tell application "Finder"', '-e', ' display dialog "Hello"', '-e', ' end tell')

At that point it calls the system function with “osascript” and that last array as arguments. When system is passed a list instead of a string it runs the command with the arguments directly instead of launching a shell. This way all the arguments are effectively quoted correctly but without ever having to worry about quotes. This is an extremely clean way of launching commands. You really should never pass system (or open) a string: (a) it needlessly launches a shell, and (b) you have to worry about quotes.

In the end, I like that it fits on one line and that it lets the Applescript sit in the program in its native form.

Edit: Also check out my Ruby version.

This entry was posted in Software and tagged , . Bookmark the permalink.

16 Responses to Calling Applescript from Perl

  1. Patrick Cronin says:

    Thanks for the sub! It’s working nicely in a script to add photos to certain albums in iPhoto based on their EXIF data. The regex in the post is appearing on your site like /n/ and not /\n/ (the backslash is missing). It wasn’t working for me for a bit, and then I realized I was splitting on the letter n, and not newline characters. All set now, and thanks for doing the leg work!

  2. david says:

    You’re right, the backslash was missing–probably from when I converted from Textmate to WordPress. I’ve fixed it. Thanks!

  3. Kurt says:

    Does not work under OS X 10.5.8. Just goes away and never comes back.

    This isn’t surprising, since the same applescript code exhibits the same behavior when run from script editor.

    However, running just: display dialog “Hello” from Script Editor gives the desired results.

    Unfortunately, trying to do the same from perl using the code above just generates the following error:

    osascript(41994) malloc: *** error for object 0xa1b1c1d3: Non-aligned pointer being freed
    *** set a breakpoint in malloc_error_break to debug
    2:24: execution error: No user interaction allowed. (-1713)

    Any insight?

  4. Kurt says:

    Ah-HA!

    Turns out this is an error from requiring “user interaction” that can be resolved with:

    tell application “AppleScript Runner”
    display dialog “it works!”
    end

  5. Caravan says:

    This works on OS X 10.5.8 for me.

    #!/usr/bin/perl
    
    sub osascript($) { system 'osascript', map { ('-e', $_) } split(/\r/, $_[0]); } 
     
    &osascript (
     'tell application "Finder"
     display dialog "Hello"
     end tell'
    )
    
  6. david says:

    Caravan, you changed the \n to \r, which means your script doesn’t have unix end-of-line characters. ‘od -c thescript.pl‘ to check your end of lines. Normally Mac OS X text files use unix-style ‘\n’ but if you were editing on an old Mac program (carbon based, from the pre-OS X days) then it might use the old Mac OS 9 style ‘\r’ end-of-lines.

    To completely fix it you could also do:

    sub osascript($) { system 'osascript', map { ('-e', $_) } split(/[\r\n]+/, $_[0]); }

    which would handle ‘\n’ (unix), ‘\r’ (old-mac) and ‘\r\n’ (windows) eols. It would also skip blank lines which is probably good too (-e ” would be pointless).

  7. Caravan says:

    Hi David,

    I didn’t really think that one through. I’m working on BBEdit 9.6.3 and I have line endings set to Unix (LF) but what is actually in my script is \r. My Perl scripts created on this work on both OSX and various flavours of linux.

    The reason I happened upon your page was because I’m doing a project using Mac::Glue. If you are not keen on Applescript or you want to have access to Perl’s text processing power and massive back catalogue of modules, including powerful encryption Mac::Glue is brilliant. Mac::Glue is a Perl Module that allows you to Control Mac apps with Apple event terminology from Perl.

    You can write a small Applescript that runs your Perl script all it has to say is:

    do shell script “perl /pathto/perlscript.pl”

    Your Perl script can then do anything and everything that a Perl script can do and then tell a Mac application to do something with the result.

  8. david says:

    Caravan, try adding this to your script:

    use Data::Dumper;
    sub osascript($) { print Dumper(['osascript', map { ('-e', $_) } split(/\r/, $_[0])]); }
    

    When I run it I get:

    $VAR1 = [
              'osascript',
              '-e',
              'tell application "Finder"
    display dialog "Hello"
    end tell'
            ];
    

    That passes the newlines embedded in the command line (which apparently works). But it makes the whole map/split part pointless (since there are no ‘\r’ chars in the string to split on). At that point you may as well just do:

    system('osascript', '-e',
    'tell application "Finder"
    display dialog "Hello"
    end tell')
    

    I haven’t seen Mac::Glue before. It looks cool. I don’t have a problem with Apple Events per se—I like the idea of universal API for applications. I just don’t like AppleScript itself. The natural language part of it never set well with me—the whole thing feels hacky. The idea of elevating AppleEvents to native (and fairly idiomatic) Perl is a good one.

  9. Caravan says:

    I see exactly the same from Data::Dumper

    $VAR1 = [
              'osascript',
              '-e',
              'tell application "Finder"
     display dialog "Hello"
     end tell'
            ];
    
  10. Kurt says:

    This all seems to work fine for opening a dialog box, but has anyone had any success in getting information BACK from an applescript?

    I’m trying to create a web page that uses AJAX to call a perl script that uses applescript to get the URL of the front page in Safari. The perl script then does some processing on the return value (which I’ve left out to simplify the code) and passes it back to the AJAX call to be inserted into a <div>.

    There are three components (in addition to the AJAX.js includes, which I will spare you): The html file, the perl file and the applescript (which is embedded in the perl file).

    Here’s the html call:

    <input type="Button" name="var1" value="test1" onclick="
    ajaxRead('http://localhost/jobz/link_recorder/test.cgi','target')" />

    <div id="target">
    start
    </div>

    It calls this perl script (with the embedded applescript):

    #! /opt/local/bin/perl -w
    use strict;
    my $url=`osascript -e 'tell application "Safari"
    set arg1 to URL of document 1
    end tell
    return arg1' `;
    print "Content-type: text/html

    --> $url <--";

    On clicking the button, I get “–> <–" in the target <div>

    If I execute the perl script from the command line, I get the expected results (the URL of the front Safari window), but for some reason, when the script is called from the html file, the results of the applescript don't appear to be making it to the $url variable.

    Any insights?

  11. Confect says:

    Looks nice. I’ve yet to confirm that it works for me, but the formatting around here seems to be playing havoc and, Perl noob that I am, I cannot determine what is right and what is wrong.

    For example, in an above comment a code box displays this (which gives me errors):
    sub osascript($) { system ‘osascript’, map { (‘-e’, $_) } split(/[\r\n]+/, $_[0]); }

    While Viewing it’s source displays this (which does not):
    sub osascript($) { system 'osascript', map { ('-e', $_) } split(/\n/, $_[0]); }

    Thanks anyhow, I’ll press ahead and see if I can put it to good use.

  12. david says:

    @Confect:

    Looks like wordpress got smart and converted the single quotes into fancy unicode quotes that don’t work with perl. It should be this:

    sub osascript($) { system 'osascript', map { ('-e', $_) } split(/[\r\n]+/, $_[0]); }

    I’ll fix the original comment so things work in the future.

    Thanks,
    David

  13. Confect says:

    Thanks, the source version was working for me anyway, but it turns out this wasn’t what I needed ^^;

    Happy coding,
    Dan

  14. Pingback: Calling Applescript from Ruby | Porkrind Dot Org Missives

  15. Pingback: eric's blog

  16. Xavier HUMBERT says:

    Thanks a lot, David ! You saved my very frustrating day ! And I agree, Applescript is a sordid piece of crap :-)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>