Beneath the beautiful Mac OS X interface lies an industrial-strength UNIX system, many of the key UNIX-based scientific applications are now available under Mac OS X. While many users feel very comfortable in the command-line environment AppleScript offers a means to access the UNIX foundations without the need to remember a series of UNIX commands. However as you might expect there are a few things that you need to be aware of when linking AppleScript and UNIX.
Leading you up the garden path
Applescript uses the colon “:” as a separator for directories however UNIX uses POSIX (Portable Operating System for UNIX) file paths in which the slash “/” is used as the directory separator. This could cause obvious problems, however one of the additions to AppleScript 1.8 was the ability to interconvert the two file reference systems.
Open up Script Editor and type/copy :-
1 2 3 4 5 6 7 8 9 10 11 12 13 |
set this_file to choose file set this_file_text to (this_file as text) display dialog this_file_text set posix_this_file to POSIX path of this_file display dialog posix_this_file set this_file_back to (POSIX file posix_this_file) as string display dialog this_file_back |
The first line allows the user to select a file, the second line converts the file reference to text and then it is displayed. The fourth line converts the Apple file reference to the POSIX path and then displays it. Of course you need to be able to do the reverse translation and the “POSIX file” command converts the POSIX path to a Mac OS X path as shown below.
Here is a very simple example of how this can be used, this takes a Macintosh Path to a folder, converts it to the POSIX path. The script then runs the Shell command “ls” to list the contents of the directory and displays the result.
1 2 3 4 5 6 7 |
set a to "Macintosh HD:usr:local:bin:" --Mac file path set p to POSIX path of a --Convert to POSIX path set the_text to (do shell script "ls " & p) display dialog the_text |
Whilst this works perfectly in most cases there are a couple of issues you should be aware of, a file or folder name may contain characters which need to be escaped to be passed to a shell command. Use quoted form to get the quoted form of a string. For example if a directory name contains a space.
1 2 3 4 5 6 7 8 9 |
set ap_sup to path to application support set p_ap_sup to POSIX path of ap_sup -- "/Library/Application Support/" -- contains a space do shell script "ls " & p -- does not work do shell script "ls " & quoted form of p -- works |
Come out from under that shell
The default shell for Mac OS X is BASH and I don’t want to get into a debate about the relative merits of the different shells that are available, the important thing to remember when running a “Do shell script” command is that do shell script always uses /bin/sh to interpret your command, not your default shell. So commands that work fine in the Terminal may not work when called using the “Do shell script” command. In addition when you use just a command name instead of a complete path, the shell uses a list of directories (known as your PATH) to try and find the complete path to the command. For security and portability reasons, do shell script ignores the configuration files that an interactive shell would read, so you don’t get the customizations you would have in Terminal. You need to use the full path to the command.
Compare the results of these two applescripts:
1 |
do shell script "echo $PATH" |
Should give you something like “/usr/bin:/bin:/usr/sbin:/sbin”
On the other hand this applescript
1 2 3 4 5 6 7 |
set the_shell_script to "echo $PATH" tell application "Terminal" activate do script the_shell_script end tell |
Will give you the complete PATH as defined in your .profile.
Having a little Fun with UNIX
This script gets a list of all the applications in the Applications folder, then finds those applications that contain the capital letter “B”, then sorts them into reverse alphabetical order and displays the result. The “|” is the pipe command, the pipe command is one of the most important commands in UNIX because it allows us to create powerful functions in a single statement. The pipe command is represented with the | character and it is used to connect the output from one command and send it as input to another command. On a Mac keyboard it is on the right-hand side next to the return key.
1 2 3 4 5 6 7 |
set a to "Macintosh HD:Applications:" set p to POSIX path of a set the_text to (do shell script "ls " & p & "| grep B | sort -r") display dialog the_text |
Here is a fun feature:-
1 2 3 4 |
set the_script to "cal" set the_text to (do shell script the_script) set the clipboard to the_text |
Run this script and then paste into a text editor, (I used BBEdit) and you should see this months calendar. Replace “cal” with “cal 10 1492” and you can see the month in 1492 when Christopher Columbus landed on a Caribbean Island.
Give me some input
This is the mechanism by which iBabel searches chemical information, of course iBabel was built using Xcode which allows the construction of rich user interfaces, and hopefully that will be the subject of a future tutorial. However we can use Applescript to provide a limited user interface, the following script asks the user to choose a file, it then uses the UNIX commandline tool OpenBabel to convert the file to the format chosen in the second dialog box and then saves it to the desktop. You will need to have installed OpenBabel You could have yet another dialog box asking to choose where to save the file but I think you can see why Xcode would be a better option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
--Get path to desktop to save result set out_path to path to desktop set out_path_posix to (POSIX path of out_path & "output_file") --Get file to convert set TheFile to (choose file with prompt "Choose a file for Conversion") set the_posix_file to POSIX path of TheFile display dialog "Choose output file format" buttons {"smi", "mol2", "sdf"} default button 3 set out_format to the button returned of the result --Generate shell script -- /usr/local/bin/babel '/Public/multimol.sdf' -osmi '/Users/username/Desktop/outputfile.smi' set babel_script to "/usr/local/bin/babel '" & the_posix_file & "' -o" & out_format & " '" & out_path_posix & "." & out_format & "'" do shell script babel_script |
It should be noted that the latest version of OpenBabel now uses obabel not babel.
Print Folder Contents Applescript
I recently needed a list of all files in a particular folder, actually the folder contained sub-folders also containing files. This tedious task becomes trivial with a couple of UNIX commands and Applescript provides a convienient user interface and the glue between the different UNIX commands.
The script is shown colour coded below. The first part of the script simply asks the user to choose the folder they want to print, and then generates a UNIX compatible POSIX path. The next part generates a temporary file to save the output into.
The next dialog asks the user if they only want to print this folder or to include sub-directories. If the response is to only print this folder then the UNIX command:
1 |
ls > target_file |
Can be used to list the directory and send the output to the temporary file.
Alternatively the command:
1 |
ls -p -C -R > target_file |
Works recursively through the file structure listing all the files and formats the output into a multi-column format.
The final part of the script prints the temporary file.
The script can be downloaded from here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
set the_folder to (choose folder with prompt "Choose a folder or volume:") set the_folder to POSIX path of the_folder tell application "Finder" to set the_temp_folder to (path to temporary items folder) as text --If you want to keep the file use --tell application "Finder" to set the_desktop to (path to desktop folder) as text set target_file to the_temp_folder & "JUNK.txt" set target_file to POSIX path of target_file display dialog "Do you want to print this folder only or include sub-directories" buttons {"Cancel", "This only", "Sub-Directories"} default button 3 if the button returned of the result is "This only" then --Print as list set the_script to "cd " & the_folder & " ls > " & target_file as text else if the button returned of the result is "Sub-Directories" then --Print recursively as formatted list set the_script to "cd " & the_folder & " ls -p -C -R > " & target_file as text else quit end if do shell script the_script set to_print to "lp " & target_file do shell script to_print --For testing --tell application "Terminal" --activate --do script to_print --end tell |
Last Updated 6 March 2023