Track changes of the defaults command

Many Mac tips—on hints.macworld.com and elsewhere around the Web—require you to use the defaults command in the Terminal. That command lets you set secret preferences that are otherwise inaccessible. For example, defaults can help you get rid of the Ping drop-down in iTunes, disable OS X's warnings about opening download files, show all files in the Finder, make the Help Viewer behave like a normal window, set half-star ratings in iTunes, shrink the Dock dramatically, dim Dock icons for hidden applications, tweak the CrashReporter, among many, many other system tweaks.

Trouble is, if you use defaults to update your system, it can be hard to keep track of all the changes you've made. It'd be handy to know what you've done for the sake of troubleshooting and moving to a new Mac. Which is why it's so cool that Hints reader dgerrity found a way to keep just such a record.

Not surprisingly, his hint involves a bit of Terminal work. What it does is create a new script called "defaults", then set things up so that whenever you enter a defaults command in the Terminal, that script runs instead. The script will look at the defaults write command you're entered, log that change (along with the original value) to a file, and then pass the command off to the real defaults command to make the actual change.

First, create our new, customized version of defaults. Open your favorite text editor, then paste the following into it:

#!/bin/bash
# Defaults - a script to record important changes to the system

lf=${HOME}/Library/Logs/com.defaults.log
function log() { echo "$(date "+%Y-%m-%d %H:%M:%S") bash $@" >> ${lf}; }

if [[ ! ${1} ]]; then
    echo "This is the script version of defaults.  Run /usr/bin/defaults to see help"
    exit 1
fi
if [[ (${1} == write) || (${1} == delete) ]]; then
    op=${1}; dom=${2}; key=${3}; shift; shift; shift; args="${@}"
    [[ (${op} == write) && ("${args}" == "") ]]  && args='""'
    log "${USER} executed defaults ${op} ${dom} ${key} ${args}"
    logger "${USER} executed defaults ${op} ${dom} ${key} ${args}"
    log "existing value: \"$(/usr/bin/defaults read ${dom} ${key})\""
    logger "existing value: \"$(/usr/bin/defaults read ${dom} ${key})\""
    /usr/bin/defaults ${op} ${dom} ${key} ${args}
    log "new value: \"$(/usr/bin/defaults read ${dom} ${key})\""
    logger "new value: \"$(/usr/bin/defaults read ${dom} ${key})\""
else
    /usr/bin/defaults $@
fi

Save that script as a file named defaults with no extension to your Desktop.

Now you need to put that defaults file where it'll do you some good. First, at the Terminal command line type cd /usr/local/bin, and press Return. Next, to copy the file, type sudo cp ~/Desktop/defaults . and press Return again. Terminal will prompt you for your password (the one for your administrator account); type that and press Return once more.

If you're lucky, you've just successfully put your new defaults script in the right place. Chances are, however, that it's not working yet. There's an easy test: Type defaults in your Terminal window and press Return. If you see the message This is the script version of defaults, you're running your customized version. If you see something else—like a massive block of text explaining all the functions of the defaults command—your work is not yet complete.

You need to tell Terminal to prioritize the new defaults command we just created in /usr/local/bin over the original (which lives in /usr/bin/). To do that, we need to edit the file that dictates such priorities. The file in question is /etc/paths. I edited mine using vim. If you know how to do that, go right ahead. If not, here's an alternative:

In Terminal, type cp /etc/paths ~/Desktop/paths and press Return. That copies the paths file from /etc to your Desktop. Find it there and open it in your favorite plain text editor. Find the line /usr/local/bin and move it above the line /usr/bin. (If your paths file doesn't have a /usr/local/bin line, just insert one above /usr/bin.) The file will likely have other paths, too—leave those alone. After saving your changes, type sudo mv ~/Desktop/paths /etc/paths into Terminal and press Return. If you're prompted to enter your password again, do so, and press Return again.

Now, close your existing Terminal window and open a new one (to ensure that Terminal notices your updated paths file). Type defaults once more. If you see the "This is the script version..." message, then you're cooking with gas. If you still don't see it, then something's gone wrong, and you can feel free to curse my name.

Assuming you do get that "This is the script version..." reply, every time you issue a defaults write command from nowon, your new script will intercept it and log it before implementing it. To test that out, try out this one:

defaults write com.apple.dock showhidden -bool true; killall Dock

(You've just told the Dock to render application icons transparent if the apps in question are hidden; killall restarts the Dock to make the command take effect. Don't worry if you've already enabled this preference; this is just a test. And if you want to reverse this effect, repeat the command above, but replace true with false.)

Now check your new log file. Go back into your favorite text editor, choose File -> Open, and then type ~ to summon the Go to the Folder text-entry box. Follow the tilde there with /Library/Logs/com.defaults.log and press Return. The file should now be highlighted in the Open window. When you open it, it should look something like this:

2011-07-19 10:58:42 bash username executed defaults write com.apple.dock showhidden -bool true
2011-07-19 10:58:42 bash existing value: "0"
2011-07-19 10:58:43 bash new value: "1"

Now you can see exactly who changed what and when. Should you ever want to stop logging your defaults changes, just delete your custom /usr/local/bin/defaults file, and you'll be back to the command's original behavior.

Subscribe to the Help Desk Newsletter

Comments