I am Polish and I have files whose names contain Polish characters. One day I was horrified to find that I could not pass such a file to an AppleScript. After much work, I finally figured out a way to pass arguments containing international characters to an AppleScript. Along the way, I learned many other things as well. I will explain this starting with simple examples, then expanding on them. In this hint you will learn the the magic shebang for an AppleScript, and some useful rules for Makefiles, in addition to the main hint itself, with an example script that opens files in Preview.
I wanted a way to run an AppleScript from the command line using the Unix shebang approach. An example of what I wanted is having the first line of a Bourne shell script be #!/bin/sh. In this way, you can simply type the name of the script in Terminal to launch it. I wanted a similar approach for an AppleScript.
[kirkmc adds: Wow, what a hint! I have to admit, my AppleScript skills are too limited to follow all of this one, but the poster went to no end to explain this in detail. What follows are nearly 4,000 words on the subject, so read on if you're interested in the very fine details.]
Here's how the code looks:
I realize that I could use a Here Document to do something similar, but if I am passing arguments, then I would need to escape them properly before they get to the AppleScript. Regardless of whether I pass arguments or not, using a Here Document makes the shell create a temporary file. This approach is therefore slightly more elegant (in my mind, you might consider it more perverse).
Here is some interaction in Terminal on 10.4:
The sky is the limit for whatever you would like to add before your AppleScript code, because the Bourne shell can parse its own input. The approach that I use is putting an AppleScript comment in the file, and the Bourne shell parses its own input until it reaches that comment. After that point lies your AppleScript itself. As a concrete example, suppose you wanted to verify that your script was called with exactly one argument, printing some usage information to standard error as a reminder if not.
Makefiles
I promised some rules for a Makefile. Well, for AppleScripts embedded in Bourne shell scripts in this manner, you do not need to add anything to your Makefile, since there are predefined suffix rules to handle this case. Suppose the above script was named shebang.sh; here is some Terminal interaction:
So that is all fine and dandy, but with this approach, the AppleScript source is in your shell script. That is good for readability, but you still have to compile the AppleScript every time you run the shell script. Is there a way to compile an AppleScript and have it run from the command line with a shebang? Sure, just make use of the resource fork in addition to the data fork. Here is some terminal interaction on 10.4 to illustrate what I mean:
So the only line in the the data fork is the shebang. The contents of the resource fork are the compiled AppleScript.
Let's say you want to do some argument checking in the shell script, or want argument passing to work in 10.3 as well; here is a recipe. Create this file and name it shebang.shosa.
You may be wondering why I used the strange .shosa extension. That is for the benefit of make. Just add this to your Makefile:
So, let's say you wanted to compile foo. You would create foo.shosa and foo.applescript. foo.shosa would contain your shebang or Bourne shell script, and foo.applescript would contain your AppleScript source. Here is an example. Say you have this AppleScript, mute.applescript:
If you do not want your .scpt files to be automatically deleted by make, just add this line to your Makefile:
Finally, I have lots of .applescript source files in a directory. It is a good idea to make one file named shebang.txt with the shebang line #!/usr/bin/osascript, and then link all of the .shosa files for scripts that do not take arguments to that one. Then with ls -l *.shosa or file *.shosa, you can quickly see which of your scripts do not take arguments, or you might surprise yourself with which scripts you thought take arguments but will not work correctly on 10.3 or earlier.
Now we are getting ready for the main hint. Suppose we wanted to create a script that would open all its arguments in Preview. Knowing what we know now, we might expect this script to work as follows:
Contents of preview.shosa:
Finally, this shell script shows how to pass an arbitrary number if arguments to an AppleScript via environment variables (eval export ARG$i=$arg in the for loop). So the first argument is passed in the environment variable "ARG1", the second in "ARG2", and so on. Also the environment variable "ARGN" is set to the number of arguments passed.
Contents of preview.applescript:
So what can you do? Fortunately, do shell script in AppleScript returns the output of a Unix command as a raw stream of bytes as "Unicode text." The trick is to get the arguments out of /bin/cat and into your AppleScript:
Contents of preview.shosa:
This output is passed into osascript with a pipe. You might think that the AppleScript would read that, but it doesn't.
Contents of preview.applescript:
I have some JPEGs with Polish characters in the filenames in my ~/Pictures folder. Amazingly (to me at least), this script now works:
There is a bit more though. For one thing, Preview in 10.3 is broken. If there are not at least two or more files without international characters in the pathname, along with ones with international characters, then the window does not come forward and the script hangs. Preview does not show the window even when the files are launched from the Finder. The workaround is to switch apps a few times and all is well. Preview in 10.4 does not have this problem. Here are some modified versions of the previous scripts that address this issue.
Contents of preview.shosa:
This version checks to see if you are using Mac OS X prior to 10.4, and if so, it implements the workarounds. You can read the script for the details if you are interested; it is not perfect but it usually works. At least the command no longer hangs on 10.3.
Contents of preview.applescript:
This may seem specific, but it illustrates the general technique of calling different subroutines in an AppleScript via the use of environment variables to chose which subroutine to call.
A final tidbit has to do with a dead end I ran into. Initially, I intended to create a fifo and write the arguments into it with NULL bytes separating the arguments like this:
Finally, there were a ton of places I found useful info that led me along. The most important of which were these references:
I wanted a way to run an AppleScript from the command line using the Unix shebang approach. An example of what I wanted is having the first line of a Bourne shell script be #!/bin/sh. In this way, you can simply type the name of the script in Terminal to launch it. I wanted a similar approach for an AppleScript.
[kirkmc adds: Wow, what a hint! I have to admit, my AppleScript skills are too limited to follow all of this one, but the poster went to no end to explain this in detail. What follows are nearly 4,000 words on the subject, so read on if you're interested in the very fine details.]
Here's how the code looks:
#!/bin/sh
exec <"$0" || exit; read v; read v; exec /usr/bin/osascript - "$@"; exit
-- Your AppleScript here
How does this work? First the file is read by /bin/sh according to the first line (the shebang #!/bin/sh). In the next line, many things happen. First, standard input is redirected to the script itself (exec <"$0"). Then the first two lines of the script are read (the two read v bits). Then /bin/sh is replaced by osascript (exec /usr/bin/osascript - "$@"), which reads the script from standard input. Remember that standard input is the third line of the file itself, by this point, so osascript will interpret anything from that point forward.
I realize that I could use a Here Document to do something similar, but if I am passing arguments, then I would need to escape them properly before they get to the AppleScript. Regardless of whether I pass arguments or not, using a Here Document makes the shell create a temporary file. This approach is therefore slightly more elegant (in my mind, you might consider it more perverse).
Here is some interaction in Terminal on 10.4:
% cat > shebang
#!/bin/sh
exec <"$0" || exit; read v; read v; exec /usr/bin/osascript - "$@"; exit
on run argv
argv
end run
^D
% chmod +x shebang
% ./shebang a b c
a, b, c
Sadly, you cannot pass arguments to your AppleScript in 10.3 or earlier in this manner. But if you have an AppleScript that does not take any arguments, it will work perfectly on 10.3 or earlier using this technique. If you need to pass arguments to your AppleScript on 10.3 or earlier, then you can pass them as environment variables like in the following example.
The sky is the limit for whatever you would like to add before your AppleScript code, because the Bourne shell can parse its own input. The approach that I use is putting an AppleScript comment in the file, and the Bourne shell parses its own input until it reaches that comment. After that point lies your AppleScript itself. As a concrete example, suppose you wanted to verify that your script was called with exactly one argument, printing some usage information to standard error as a reminder if not.
#!/bin/sh
# check args
case "$#" in
1)
arg=$1; export arg
;;
*)
echo 'Usage: shebang arg' >&2
exit 2
;;
esac
# redirect stdin
exec <"$0" || exit
# find the start of the AppleScript
found=0
while read v; do
case "$v" in --*)
# file offset at start of AppleScript
found=1; break
;;
esac
done
case "$found" in
0)
echo 'shebang: AppleScript not found' >&2
exit 128
;;
esac
# run the AppleScript
exec /usr/bin/osascript -; exit
-- AppleScript starts here
set arg to system attribute "arg"
First the shell script does some argument checking to see if there was an argument passed to the script (the case statement). If there was, it sets the value of the argument to an environment variable named "arg" (arg=$1; export arg). Then it reads itself until it finds a line starting with an AppleScript comment (the while loop). It will find the line "-- AppleScript starts here". It is very important that such a comment be the first line in your AppleScript embedded at the end of the shell script. The AppleScript then gets the argument from the environment variable arg (set arg to system attribute "arg"). Thereafter, your AppleScript can use the variable arg throughout.
Makefiles
I promised some rules for a Makefile. Well, for AppleScripts embedded in Bourne shell scripts in this manner, you do not need to add anything to your Makefile, since there are predefined suffix rules to handle this case. Suppose the above script was named shebang.sh; here is some Terminal interaction:
% make -f /dev/null shebang
cat shebang.sh >shebang
chmod a+x shebang
Basically, that is all you need for an AppleScript embedded in a Bourne Shell script; give your source a filename of foo.sh, and make foo will create it. You do not have to add anything more to your Makefile.
So that is all fine and dandy, but with this approach, the AppleScript source is in your shell script. That is good for readability, but you still have to compile the AppleScript every time you run the shell script. Is there a way to compile an AppleScript and have it run from the command line with a shebang? Sure, just make use of the resource fork in addition to the data fork. Here is some terminal interaction on 10.4 to illustrate what I mean:
% /usr/bin/osacompile -o shebang.scpt
on run argv
argv
end run
^D
% chmod +x shebang.scpt
% echo \#\!'/usr/bin/osascript' > shebang.scpt
% ./shebang.scpt a b c
a, b, c
First you compile the AppleScript into the resource fork of shebang.scpt (/usr/bin/osacompile -o shebang.scpt). Then you type the AppleScript itself, finishing by pressing Control-D on your keyboard. Then you make the compiled AppleScript executable (chmod +x shebang.scpt). Finally, you replace the (currently empty) data fork of the compiled AppleScript with the shebang #!/usr/bin/osascript.
So the only line in the the data fork is the shebang. The contents of the resource fork are the compiled AppleScript.
Let's say you want to do some argument checking in the shell script, or want argument passing to work in 10.3 as well; here is a recipe. Create this file and name it shebang.shosa.
#!/bin/sh
# check args
case "$#" in
1)
arg=$1; export arg
;;
*)
echo 'Usage: shebang arg' >&2
exit 2
;;
esac
# run the AppleScript
exec /usr/bin/osascript -- "$0"
That was the Bourne shell script part that was previously described. Then create this file and name it shebang.applescript:
set arg to system attribute "arg"
Now if you do the following from Terminal, it will put the two halves together and run:
% osacompile -o shebang.scpt shebang.applescript
% chmod +x shebang.scpt
% cat shebang.shosa > shebang.scpt
% ./shebang.scpt 'Hello, world!'
Hello, world!
So the only additional trick is to replace the /bin/sh with osascript (exec /usr/bin/osascript -- "$0") using the compiled script itself.
You may be wondering why I used the strange .shosa extension. That is for the benefit of make. Just add this to your Makefile:
.SUFFIXES : .applescript .scpt
.applescript.scpt : ; osacompile -o '$(subst ','\'',$*)'.scpt -- '$(subst ','\'',$<)'
% : %.scpt %.shosa
ditto -rsrc '$(subst ','\'',$*)'.scpt '$(subst ','\'',$@)'
chmod a+x '$(subst ','\'',$@)'
cat -- '$(subst ','\'',$*)'.shosa > '$(subst ','\'',$@)'
The lines under % : %.scpt %.shosa must all begin with tabs instead of spaces. That is how make knows what are make commands and what are shell commands. Also, since we know we are using AppleScript on a Mac, we might as well use some GNU-specific make extensions such as subst. This is particularly useful for AppleScript, because I love to give my AppleScripts names containing all sorts of strange characters, such as spaces and apostrophes. The idea is for my AppleScripts to have meaningful names in the AppleScript menu, and using subst is what gets everything quoted correctly for the shell.
So, let's say you wanted to compile foo. You would create foo.shosa and foo.applescript. foo.shosa would contain your shebang or Bourne shell script, and foo.applescript would contain your AppleScript source. Here is an example. Say you have this AppleScript, mute.applescript:
-- toggle the sound on/off from the command line
set myVolume to get volume settings
if output muted of myVolume is false then
set volume with output muted
else
set volume without output muted
end if
This AppleScript does not take any arguments, so you might as well use this as the contents of your mute.shosa:
#!/usr/bin/osascript
Then type this make command:
% make mute
osacompile -o 'mute'.scpt -- 'mute.applescript'
ditto -rsrc 'mute'.scpt 'mute'
chmod a+x 'mute'
cat -- 'mute'.shosa > 'mute'
rm mute.scpt
% ./mute
Now I can use the mute command just created to turn off email notifications from the Mac across the room via ssh, without moving my butt from my chair.
If you do not want your .scpt files to be automatically deleted by make, just add this line to your Makefile:
.PRECIOUS : %.scpt
You may often you like to create .scpt AppleScripts to drop into your ~/Library/Scripts folder. This same bunch of rules in a Makefile will allow you to do that as well. Just type make foo.scpt, and the foo.scpt compiled AppleScript will be generated from foo.applescript.
Finally, I have lots of .applescript source files in a directory. It is a good idea to make one file named shebang.txt with the shebang line #!/usr/bin/osascript, and then link all of the .shosa files for scripts that do not take arguments to that one. Then with ls -l *.shosa or file *.shosa, you can quickly see which of your scripts do not take arguments, or you might surprise yourself with which scripts you thought take arguments but will not work correctly on 10.3 or earlier.
Now we are getting ready for the main hint. Suppose we wanted to create a script that would open all its arguments in Preview. Knowing what we know now, we might expect this script to work as follows:
Contents of preview.shosa:
#!/bin/sh
case $# in
0)
echo "Usage: ${0##*/} file [ file... ]" >&2
exit 1
;;
esac
i=0
for arg in "$@"; do
let 'i = i + 1'
case "$arg" in
/*)
;;
*)
arg=$PWD/$arg
;;
esac
eval export ARG$i=\$arg
done
ARGN=$# exec /usr/bin/osascript -- "$0"
Again, this is AppleScript, so we might as well use features common to zsh and bash in our script, since those are what /bin/sh is on a Mac. This shell script also shows a couple of useful tricks. In bash and zsh, ${0##*/} is the basename of the script. You can do integer arithmetic like this: let 'i = i + 1'. Relative paths often do not work in AppleScript, so you need to change all the relative paths to absolute paths. All absolute paths start with a slash (/); otherwise you can put the current working directory in front of the relative path to make it absolute. That is all that is done in the case statement.
Finally, this shell script shows how to pass an arbitrary number if arguments to an AppleScript via environment variables (eval export ARG$i=$arg in the for loop). So the first argument is passed in the environment variable "ARG1", the second in "ARG2", and so on. Also the environment variable "ARGN" is set to the number of arguments passed.
Contents of preview.applescript:
tell application "Preview" to activate
set argn to system attribute "ARGN"
set arg to system attribute "ARG" & argn
set argv to {}
repeat argn times
set arg to system attribute "ARG" & argn
set arg to POSIX file arg
set the beginning of my argv to arg
set argn to argn - 1
end repeat
tell application "Preview" to open my argv
Let's see if it works: create this Makefile (remembering to replace what looks like eight spaces with tabs):
.SUFFIXES : .applescript .scpt
.PHONEY : install
preview :
PREFIX = /usr/local
BINDIR = ${PREFIX}/bin
install : preview
mkdir -p ${BINDIR}
ditto -rsrc $< ${BINDIR}
.applescript.scpt : ; osacompile -o '$(subst ','\'',$*)'.scpt -- '$(subst ','\'',$<)'
% : %.scpt %.shosa
ditto -rsrc '$(subst ','\'',$*)'.scpt '$(subst ','\'',$@)'
chmod a+x '$(subst ','\'',$@)'
cat -- '$(subst ','\'',$*)'.shosa > '$(subst ','\'',$@)'
In Terminal type this:
% make preview
osacompile -o 'preview'.scpt -- 'preview.applescript'
ditto -rsrc 'preview'.scpt 'preview'
chmod a+x 'preview'
cat -- 'preview'.shosa > 'preview'
rm preview.scpt
% sudo make install
Password:
mkdir -p /usr/local/bin
ditto -rsrc preview /usr/local/bin
% rehash; hash -r
% /usr/local/bin/preview /Library/Desktop\ Pictures/*.[Jj][Pp][Gg] \
/Library/Desktop\ Pictures/*/*.[Jj][Pp][Gg] \
/System/Library/Screen\ Savers/*.slideSaver/Contents/Resources/*.[Jj][Pp][Gg]
This AppleScript creates a list, argv, that gets arguments from the environment variables, but, as I alluded to in the very beginning of this hint, such a script will not work with pathnames that contain international characters. The problem is that system attribute returns a "string" and not a "Unicode text" in AppleScript. Therefore, pathnames with international characters get munged according to your text encoding, in my case macroman. What is even worse is that the argument passing ability of AppleScript added in 10.4 also treats the arguments as "string": real scripting languages like perl, python, and even the Bourne shell treat arguments as a raw string of bytes and you need to ask for the arguments to get munged on your behalf.
So what can you do? Fortunately, do shell script in AppleScript returns the output of a Unix command as a raw stream of bytes as "Unicode text." The trick is to get the arguments out of /bin/cat and into your AppleScript:
Contents of preview.shosa:
#!/bin/sh
case $# in
0)
echo "Usage: ${0##*/} file [ file... ]" >&2
exit 1
;;
esac
{
case "$1" in
/*)
arg=$1
;;
*)
arg=$PWD/$1
;;
esac
echo -nE "$arg"
shift
for arg in "$@"; do
case "$arg" in
/*)
;;
*)
arg=$PWD/$arg
;;
esac
echo -ne '\x00'; echo -nE "$arg"
done
} | /usr/bin/osascript -- "$0"
So what is this shell script doing that is new? It writes all of the arguments to standard output with a NULL byte between each argument. That is what the echo lines do. Now since the NULL byte is used to signify the end of a C-style string, you should not find any pathnames with a NULL byte in them. Also, because of how UTF-8 is defined, you will never have a NULL byte appear in a multibyte character. So it is perfectly safe to do this.
This output is passed into osascript with a pipe. You might think that the AppleScript would read that, but it doesn't.
Contents of preview.applescript:
tell application "Preview" to activate
set argv to do shell script "/bin/cat"
set AppleScript's text item delimiters to ASCII character 0
set argv to argv's text items
set AppleScript's text item delimiters to {""}
repeat with i from 1 to number of items in my argv
set this_item to item i of my argv
set item i of my argv to POSIX file this_item
end repeat
tell application "Preview" to open my argv
The AppleScript does not touch standard input in any way. In fact, it calls the Unix command cat with set argv to do shell script "/bin/cat". Remember that the standard input of the AppleScript is the output of the pipe in the previous shell script. Therefore, the input of /bin/cat is that same output. cat just spits its input back out into the result of do shell script in the AppleScript. This return value is a raw string of bytes as a "unicode string." Then we set the text delimiter with set AppleScript's text item delimiters to ASCII character 0 to the NULL byte, and use that to break up the long string into a list of the arguments separated by NULL bytes with set argv to argv's text items.
I have some JPEGs with Polish characters in the filenames in my ~/Pictures folder. Amazingly (to me at least), this script now works:
% make preview
osacompile -o 'preview'.scpt -- 'preview.applescript'
ditto -rsrc 'preview'.scpt 'preview'
chmod a+x 'preview'
cat -- 'preview'.shosa > 'preview'
rm preview.scpt
% sudo make install
Password:
mkdir -p /usr/local/bin
ditto -rsrc preview /usr/local/bin
% rehash; hash -r
% /usr/local/bin/preview ~/Pictures/*.[Jj][Pp][Gg]
So we have basically reached the end of this hint. You now know how to pass arguments with international characters into an AppleScript. Along the way you might have picked up a few tidbits too. I hope you had fun.
There is a bit more though. For one thing, Preview in 10.3 is broken. If there are not at least two or more files without international characters in the pathname, along with ones with international characters, then the window does not come forward and the script hangs. Preview does not show the window even when the files are launched from the Finder. The workaround is to switch apps a few times and all is well. Preview in 10.4 does not have this problem. Here are some modified versions of the previous scripts that address this issue.
Contents of preview.shosa:
#!/bin/sh
case $# in
0)
echo "Usage: ${0##*/} file [ file... ]" >&2
exit 1
;;
esac
argio() {
case "$1" in
/*)
arg=$1
;;
*)
arg=$PWD/$1
;;
esac
echo -nE "$arg"
shift
for arg in "$@"; do
case "$arg" in
/*)
;;
*)
arg=$PWD/$arg
;;
esac
echo -ne '\x00'; echo -nE "$arg"
done
}
pv=`/usr/bin/sw_vers -productVersion`
case "$pv" in
10.[0123].*)
;;
*)
argio "$@" | /usr/bin/osascript -- "$0"
exit
;;
esac
unset fg; unset pid; unset app
app=`fg=1 /usr/bin/osascript -- "$0"`
trap : USR1
argio "$@" | pid=$$ /usr/bin/osascript -- "$0" &
wait
export app;
/usr/bin/osascript -- "$0"
This version checks to see if you are using Mac OS X prior to 10.4, and if so, it implements the workarounds. You can read the script for the details if you are interested; it is not perfect but it usually works. At least the command no longer hangs on 10.3.
Contents of preview.applescript:
set sysv to system attribute "sysv"
if sysv is less than 4160 then
set env to system attribute
if my env contains "fg" then
tell application "System Events" to name of first application process whose frontmost is true
return result
end if
if my env contains "app" then
set arg to system attribute "app"
tell application arg to activate
tell application "Preview" to activate
return
end if
end if
tell application "Preview" to activate
set argv to do shell script "/bin/cat"
set AppleScript's text item delimiters to ASCII character 0
set argv to argv's text items
set AppleScript's text item delimiters to {""}
repeat with i from 1 to number of items in my argv
set this_item to item i of my argv
set item i of my argv to POSIX file this_item
end repeat
if sysv is less than 4160 then
try
try
set pid to system attribute "pid"
do shell script "/bin/kill -USR1 " & pid
tell application "Preview" to open my argv
on error e number -609
-- Connection is invalid.
end try
on error e number -1712
-- AppleEvent timed out.
end try
else
tell application "Preview" to open my argv
end if
This may seem specific, but it illustrates the general technique of calling different subroutines in an AppleScript via the use of environment variables to chose which subroutine to call.
A final tidbit has to do with a dead end I ran into. Initially, I intended to create a fifo and write the arguments into it with NULL bytes separating the arguments like this:
set fifodir to system attribute "fifodir"
set fifo to fifodir & "/fifo"
set the_input to open for access POSIX file fifo
-- We can't do this because AS does an open for every read!
-- do shell script "rm -rf " & quoted form of fifodir
set linefeed to (ASCII character 0)
set argv to {}
repeat
try
-- «class utf8»
set linebuf to read the_input before linefeed as text
set end of my argv to POSIX file linebuf
on error e number -39 -- End of file error.
close access the_input
exit repeat
end try
end repeat
my argv
But unfortunately, this does not work. It seems that when I use as text or as «class utf8» in the set linebuf to read the_input before linefeed line, the line is munged (at least on my intel iMac). I think that what is going on is that AppleScript only really understands UTF-16 and is converting incorrectly, but that is only a hunch. If I use as unicode text that is even worse; I just get garbage that looks Chinese. But anyway, AppleScript seems to be pretty slow at reading, so there probably would not have been much improvement to the version the uses /bin/cat . But if anyone knows how to make the fifo/file approach work, please let me know.
Finally, there were a ton of places I found useful info that led me along. The most important of which were these references:
- AppleScript: The Definitive Guide, by Matt Neuburg - O'Reilly Press
- Mac OS X Hints - A shell/AppleScript interaction trick
- alt.comp.lang.applescript - osascript and encoding of CLI arguments
- Wikipedia - UTF-8
- Mac OS X Hints - Open many images in Preview from Terminal
- alt.comp.lang.applescript - Example of reading stdin and writing stdout in Applescript
•
[23,346 views]

