Thursday, April 10, 2008

AppleScript, Shell, and stdin

AppleScript can be called from shell scripts, effectively giving access to Mac OS X applications from the underlying Unix tools. The AppleScript is embedded into the shell script as a here document, and invoked using osascript. I've given an example of this approach before.

However, there is something that I've never really been clear on. How does the AppleScript portion of the shell script read from stdin? Standard I/O is fundamental to Unix programming, so it is essential that AppleScript be able to access stdin. Searching with Google hasn't enlightened me. There seems to be no useful parallel with stdout, which works as you'd hope, with osascript writing transparently to it.

After a bit of thinking, I came up with this:
#!/bin/sh

STANDIN=$(mktemp /tmp/seereport.XXXXXXXXXXXX) || exit 1
cat > $STANDIN

/usr/bin/osascript > /dev/null <<ASCPT
    set stdinText to do shell script "cat $STANDIN"
    tell application "TextEdit"
        activate
        make new document with properties {text:stdinText}
    end tell
ASCPT

trap 'rm -f $STANDIN' EXIT


I write stdin to a temporary file using cat, then read it back out in the AppleScript portion, again using cat. As an example, I just open a new TextEdit document with the text from stdin as its contents.

Saving this as minimal.sh, I can then create a new TextEdit document from the shell with:
echo hello world | ./minimal.sh

This works, so I've managed to get at stdin. It seems pretty roundabout though. Is there a better or recommended approach?

Update: I suppose it is worth mentioning that one could use pbcopy and pbpaste to avoid using the temporary file. However, doing so modifies the clipboard, so I prefer the approach shown.

Further, it would be possible to read the temp file using AppleScript commands, instead of calling cat with do shell script. That's too fiddly for a minimal example. Beyond that, I don't see much point to it, since I'd do any processing in the shell, just using AppleScript to pass the text to an application. I can't think of any applications where a stream approach would buy us anything.

Update: Modified example of using minimal.sh to actually use minimal.sh!

2 comments:

edward said...

here's a simple alternative to read from stdin :D

#!/bin/sh
# Converts all lowercase text from
# stdin to uppercase
#
tr '[:lower:]' '[:upper:]' < /dev/stdin


# edward
# ebaddouh@gmail.com

Michael Barber said...

Thanks for the reminder on /dev/stdin. See also the next post.