Scripting Mac OS Xby James Reynolds
Editor's Note: James Reynolds recently spoke at the O'Reilly Mac OS X Conference as part of a panel titled "Migration to Mac OS X, A Case Study of Higher Education Institutions." In that session, James talked about the value of automating tasks in the educational environment. Of course, when you think of automation, you think of scripts. So as a follow up, James put together this hands-on tutorial to help you master scripting Mac OS X, complete with a helpful appendix of sample scripts (on page 3). This is one you'll want to have handy for reference.
Mac OS X is an awesome platform to administer. The ability to control the user experience with scripts is astounding. This OS has everything a scripter wants: a command line, languages, remote login, and dependability. And it's all built in.
This tutorial strives to share tips for administrators who are relatively new to the platform (but there are some good tips for veterans too). First I'll cover learning Perl and shell scripts. Then I'll discuss how to execute Perl, shell, or AppleScript commands from a script that is written in a different language. As part of this bargain, I'll show three common scripting mistakes. Finally, I'll go through the steps of setting up a script that runs every login and show some of the problems that might be encountered. At the end of this tutorial, there is a side-by-side comparison of common script tasks in Perl, BASH, and AppleScript.
In order to work through this tutorial, you will at least need to know how to create and execute either a Perl or shell script. It also helps to know the Terminal command line, some Terminal commands, and some Perl. You can refresh your Terminal hacks by taking a look at Chris Stone's Learning the Terminal series right here on Mac DevCenter.
The Terminal is a powerful tool, and every administrator should develop some basic skills using it.
It is important to learn how to use the Terminal because shell scripts are the same as typing commands in the Terminal. There are only a few Terminal commands that cannot be placed straight into a script. If you can perform your tasks using the Terminal, you have already written your script! Just copy the commands that you type in the Terminal and paste them into a text document. With a little extra formatting and cleanup, you have a shell script.
There are several shell script languages. In Mac OS X, the most common shell script is BASH. The default Terminal shell language is TCSH in 10.1 and 10.2, and BASH in 10.3. You should set the Terminal to use the same language you are going to script in so that you have experience using that shell and know exactly how a command will behave. Most shell scripts are written using
/bin/sh, so you should change the command line to
/bin/sh by just typing
/bin/sh. You can write shell scripts in any language. But you need to be aware that each language behaves differently.
The other common administrator non-shell language is Perl. Perl syntax is very different than that of shell scripts. To write scripts using Perl, you should read Learning Perl, 3rd Edition. This is the book I read when I learned Perl.
Executing Shell Commands from Perl
If you know a little Perl and a little shell, you can actually mix
both languages in a script by calling the system command in Perl or
executing code using
Perl -e ... in a shell script.
For example, a Perl script can read the contents of a file without using Perl's file commands with this code:
The backticks (
#!/usr/bin/perl $system_crontab = `cat /etc/crontab`; print $system_crontab;
``) tell Perl to execute the shell command
cat /etc/crontab. The output of the
catcommand is stored in the
$system_crontabvariable and printed in the last line.
Executing Perl from Shell Scripts
Likewise, a shell script could call Perl to get the fifth file of the /etc folder with:
#!/bin/sh file_six=`/usr/bin/perl -e '@file_list=\`ls -1 /etc\`;' -e 'print $file_list;'` echo $file_six
The backticks in this example perform the same function that they do
in Perl. They are used to execute a shell command (in this case, the
command is the Perl interpreter) and save the output into the
-e flag that follows the Perl command tells the Perl
interpreter to execute the string
'@file_list=\`ls -1 /etc\`;' -e
'print $file_list;'. Rolling this out, Perl code that is executed
looks like this:
@file_list=`ls -1 /etc`; print $file_list;
The first line,
'@file_list=`ls -1 /etc`;', has another pair of backticks, which execute
ls -1 /etc in the shell environment. The output is assigned to the
file_list array. Notice that these backticks have to be escaped with the backslash (
\), because the code is already inside of a pair of backticks.
The second line prints the sixth element of the
file_list array. Perl array indexes begin with 0, so the sixth element has an index of 5.
That print statement doesn't print to the command line, though. The output of the Perl script is "printed" back to the shell script and it is assigned to the
file_six shell variable. It is printed to the command line with the
echo command that is in the last line of the shell script.
Executing AppleScript from Shell and Perl Scripts
One of the jewels of Mac OS X scripting is the
osascript command. It is used just like the example above with the shell script that executes Perl. The following shell script will display a dialog:
#!/bin/sh osascript -e 'tell application "Finder"' -e "activate" -e "display dialog \"hello\"" -e 'end tell'
In this example, the shell script executes the
osascript command. The
osascript command executes this AppleScript:
tell application "Finder" activate display dialog "hello" end tell
To execute an AppleScript from Perl, simply put the
osascript command in backticks, or execute it with the
system command. This Perl script will use Text-To-Speech to "say" a message.
#!/usr/bin/perl $message = "hello"; system "osascript -e 'say \"$message\"'";
Executing Shell and Perl Scripts from AppleScript
Finally, it is even possible to execute shell scripts with the
do shell script AppleScript command.
The following AppleScript command will display a dialog that contains all of the files in /etc that contain the letter "b."
display dialog (do shell script "ls /etc | grep b")
Similarly, if the
do shell script contained the
Perl -e command, then you could execute Perl from AppleScript, as well.
Common Script Mistakes: Line Endings
Perhaps the most common scripting error occurs when a script is created with BBEdit or some other text editor and the file is saved with Macintosh line endings. There is a Macintosh new line character, ASCII 13, and there is a UNIX new line character, ASCII 8. They are both valid in text documents, but UNIX will not treat a Macintosh line ending as the end of a line.
If you try to execute a script that has Macintosh line endings, you will get a "Command not found" error:
/path/to/script: Command not found.
There are a few ways to change the line endings from Macintosh to UNIX. The easiest is to open the document in BBEdit. Each document has a tool bar at the top. The fifth tool looks like a small document icon. Click on it and a pop-up menu appears. Scroll down and select "UNIX," and then save the script.
To change the line endings from the command line, execute this command in the Terminal:
tr \\r \\n < /path/to/script > /path/to/new_script
/path/to/script with the path to the script with Macintosh line endings, and
/path/to/new_script to where you want to save the new file. The two paths cannot be the same, or you will end up creating an empty file.
Common Script Mistakes: Execute Permissions
Another common mistake is incorrect execute permissions. In order to execute a script, the file must have execute permissions. If you try to execute a file that does not have execute permissions, you will get the following error:
/path/to/script: Permission denied
By default, all new text documents do not have execute permissions. To add execute permissions to a script, open the Terminal and type the following command:
"chmod u+x /path/to/script"
If you do not want to type the script path, you can always type
chmod u+x and drag the script file to the Terminal window, and
the Terminal will fill in the path. Then hit return.