oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button

Scripting Mac OS X
Pages: 1, 2, 3

Common Script Mistakes: Bad White Space

Related Reading

Learning Perl
By Randal L. Schwartz, Tom Phoenix

Another problem occurs when copying script text from web pages. Many web pages that show scripts contain the special HTML code &nbsp;. This code creates a whitespace character and it is used for script formatting on a web page. The problem with this is that Safari and Internet Explorer display this space with ASCII character 194 or 202 instead of the regular space character, ASCII character 32. While on the web page it may look like a space, if you copy it straight from the browser and paste the text into BBEdit or the Terminal, it will still look like a space, but when you try to execute the script, you will get an error.

A shell script with a bad whitespace character will produce an error that looks like this:

</path/to/script>:  <command>: command not found

</path/to/script> would be the path to your script, and <command> would be replaced with the line in your code that has the bad whitespace character.

A Perl script will give one of these errors:

Unrecognized character \xC2 at <filename> line <number>


Unrecognized character \xC4 at <filename> line <number>

To fix this, you need to remove all of the bad whitespace characters and replace them with ASCII 32, the character produced by the space bar on your keyboard. In BBEdit, you can do this by finding one of the bad characters and performing a find and replace. Paste the bad character into the find field, and type a normal space character in the replace field. Then check "Start at Top" and click the "Replace all" button.

Real-World Scripting Example

Now I would like to set up a script to perform a common computer-lab task that is performed at login. It will remove the contents of the folder /Users/labuser. Then the script will restore /Users/labuser with a brand-new home folder and give ownership of the folder to the user who is logging in. There are many hidden problems just in this simple task.

Here is the script, in BASH:


rm -rf "/Users/labuser"
ditto -rsrcFork "/System/Library/User Template/English.lproj" "/Users/labuser"
chown -R $1:staff "/Users/labuser"

First of all, let's try to execute each command, one line at a time in the Terminal. Before you do this, you need to create a home folder that the script will erase. Open up System Preferences and create a user account named "labuser". This will create a folder at /Users/labuser. Don't create a folder in the Finder or the Terminal, because then the folder will not have the correct ownership.

Second, enter the same shell as your script by typing /bin/sh in the terminal. This should guarantee that everything you type will behave exactly the same as when you run the script for real.

When you try to execute the first line, you should get this error:

rm: /Users/labuser: Operation not permitted

The solution to this is to execute the command with elevated permissions. You can do this by typing

sudo /bin/sh

Then enter your password.

This brings up a point that you need to be aware of when writing scripts. Most system tasks require root permission. You cannot place sudo in any script, because it will ask for a password, and you should never hardcode a password in a script. So you need to execute the script by the root user. In a few paragraphs, I will show you how to execute the script every login as a LoginHook. The LoginHook will execute as root; that is how you can avoid using sudo. LogoutHooks, startup items, and system cron jobs also execute as root. For now, to finish testing the script, just type sudo /bin/sh and your password to become root.

Start entering each line again. This time, you should not get the "Operation not permitted" error. However, the last line will produce an error. $1 is a special variable that is set when the script is executed. Since we aren't executing this as a script yet, you need to replace $1 with labuser, like this:

chown -R labuser:staff "/Users/labuser"

Then it executes as expected.

Save the commands in a text file and make sure the file has user execute permissions. Attempt to execute the script from the Terminal. Just type in the path to the text file, either the full path or a relative path (/Users/yourname/Desktop/loginhook, ~/Desktop/loginhook, or whatever the path is). You will need to give it a value for $1. Do this by placing labuser after the path, like so:

/Users/yourname/Desktop/loginhook labuser

The script executes as expected. You can make sure it is deleting files by adding extra files to the labuser folder and checking to see if they are gone after running the script. You can confirm that the ownership of the folder is also correct by typing:

ls -ld "/Users/labuser"

It should be:

drwxr-xr-x  2 labuser  staff  68 Sep 30 02:07 /Users/labuser/

Now, let's actually set this up as a LoginHook and execute it by logging in to the computer.

The LoginHook functionality of Mac OS X is built into Mac OS X's loginwindow application. Apple had the foresight to add this feature and to document it in "Mac OS X: System Overview." Before you set up the LoginHook, you should decide where to place all your administrator scripts. Everyone has a different preference. Most administrators create a dedicated administration folder in /Library. The folder could get quite cluttered as you add more functionality, so you may want to save it in a scripts folder as well, such as /Library/Admin/scripts/

To set up the LoginHook, type the following command into the Terminal, replacing /path/to/script with the path to your script.

sudo defaults write LoginHook /path/to/script

Now log out, and log back in as the admin user. When you log in, the LoginHook should execute. Now we need to check to see if it worked! Examine the contents of /Users/labuser by typing the following in the Terminal:

ls -ld "/Users/labuser"

You should see:

drwxr-xr-x  2 root  wheel  68 Sep 30 02:15 /Users/labuser/

The folder is replaced, but the permissions are incorrect. For some reason, the chown command did not execute.

Why didn't the script execute the chown command? To find the solution, you need to capture the output of the command. Edit your LoginHook and change the chown line to:

chown -R $1:staff "/Users/labuser" &> /var/log/loginhook.debug

Now log out and log back in as admin. Look at the file /var/log/loginhook.debug by typing this in the terminal:

less /var/log/loginhook.debug

You will see:

/path/to/script: chown: command not found

The shell could not find the chown command when it ran at login. However, it can find the chown command when running in the Terminal. That is because the PATH environment variable is different when running at login.

So how do you fix this? One way is to modify the LoginHook's PATH environment variable. But the best way is to just tell the script where the command is, then it doesn't even have to go look. To find a command, type "whereis " and then the command. Here is the above script with all of the paths filled out:


/bin/rm -rf "/Users/labuser"
/usr/bin/ditto -rsrcFork "/System/Library/User 
Template/English.lproj" "/Users/labuser"
/usr/sbin/chown -R $1:staff "/Users/labuser"

After saving the change, log out and then back in. The LoginHook should work now. You can now log in as labuser, make changes, log out, and log back in as labuser and notice that the changes you made are gone.

There are many tasks that can be performed at login. The ditto command is quite time-intensive as well. It is possible to run the ditto command before the LoginHook, such as at startup, or even create an idle script that runs constantly, detects when the computer is idle, and creates a cache of home folders.

In a public lab setting, there are many tasks that you want to perform at logout, startup, and other times. While still in its infancy, ULabMin is a project that contains scripts that run at login, logout, and startup, and when idle or at night.

Pages: 1, 2, 3

Next Pagearrow