Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

Track changes made with the defaults command System
There are many hints that involve the use of the defaults command to change system settings. When trying to keep my Macs in sync (as well as setting up new ones), I have difficulty remembering which changes I've made. So after some work I was able to build a script that nicely tracks the changes I make to the system.

My requirements were to log any writes or deletes, showing both the old value and the new value to a log file in ~/Library/Logs/ where it is accessible to Console. (I use logger too but those messages get lost in the noise and are aged off). I wanted to leave any other defaults command (read, find, etc.) alone.

My first try was to write a function in my .bash_profile, which is loaded at every login and overrode the executable. That seemed fine until I realized that when using sudo defaults, it wasn't working. This is because sudo simply executes a command as different user without logging in and getting the benefit of profiles. It changes certain environment variables and drops all functions and aliases. So the most important changes of all were missed.

My solution was to create a script and place it in the path prior to /usr/bin/defaults. Thus when sudo calls the command without profiles, it runs the script instead of the binary (you could also do this with a link if the path is a problem).

I export a variable with the path to log file and define a log function that includes the date/time in my .bashrc so it's available to every bash shell invoked (login or not) under my username. You can easily just put these lines in the script itself, but it turns out to be convenient to be able to log anything at the terminal with the function, so I define it in ~/.bashrc:
export lf=${HOME}/Library/Logs/com.yyyy.log
function log() { echo "$(date "+%Y-%m-%d %H:%M:%S") bash $@" >> ${lf}; }
Here is the script 'defaults':
#!/bin/bash
# Defaults - a script to record important changes to the system
source ${HOME}/.bashrc
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
Note that the single and double quotes are difficult to see but nonetheless important. Also note the usual complaints by defaults when changing a key that does not exist still apply -- it complains but does actually make the change.

[crarko adds: I haven't tested this one.]
    •    
  • Currently 3.50 / 5
  You rated: 4 / 5 (8 votes cast)
 
[5,518 views]  

Track changes made with the defaults command | 5 comments | Create New Account
Click here to return to the 'Track changes made with the defaults command' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Track changes made with the defaults command
Authored by: BiL Castine on Jul 03, '11 01:16:40PM

thanks for this hint. I tested it and it works as described.

I had been keeping a manual log but this is MUCH better.



[ Reply to This | # ]
Track changes made with the defaults command
Authored by: auntchilada on Jul 04, '11 06:03:03PM

hmm, i like this idea. going to have to think about a more efficient method.



[ Reply to This | # ]
Track changes made with the defaults command
Authored by: Andrew J Freyer on Jul 05, '11 03:43:20PM
It might be more efficient and update-proof to run this "history" command periodically instead of this intercept method. In fact, your situation is exactly what the "history" utility is for. So long as your "defaults" instructions were in the last five hundred shell commands, running: history | grep "defaults" will show what you've recently done with the "defaults" command.
---



[ Reply to This | # ]
Track changes made with the defaults command
Authored by: dgerrity on Jul 11, '11 05:30:10PM
Interesting idea, because then you could log other specific commands as well, such as
history | grep "defaults\|plist\|sudo" | grep -v history | cut -c8-
I just tried it now and there were no default entries in my past 500, so with this approach you'd probably want to run the scan relatively frequently and possible deal with duplicates. One problem with this and a problem in general with my log function is that it can't really take the output of a command with multiple lines and log it "correctly". I have to do something like
log "port list outdated"; (IFS="|"; port list outdated | while read line; do log ${line}; done)
Suggestions on a better log function that could handle multiple lines but still keep the same prefix (date, user, function?)

[ Reply to This | # ]
Track changes made with the defaults command
Authored by: dgerrity on Aug 29, '11 11:50:04AM
An improvement on the log function and an answer to my own question about logging multi-line output For the log function, I indent with shell levels so you can see in the log when scripts call other scripts; also, because $0 is often "-bash" and the dash is interpreted as an option, I strip a leading dash from the name of the calling function.

log () {
    sp=$(printf "%$(((${SHLVL}-1)*4))s" " ");
    echo "$(date "+%Y-%m-%d %H:%M:%S")${sp}$(echo $0 | sed 's/^-//') $@" >> "${lf}"
}
And secondly, a way to put multiple lines into the log (duh):

port list installed | while read line; do log $line; done


[ Reply to This | # ]