At the top of each of my shell scripts, you will see a block line this:
SOURCE="${HOME}/.source" if [ -r "${SOURCE}" ] then . "${SOURCE}" else echo "$0: no ${SOURCE} found" fi
so I thought it would be a good idea to explain what that is, why mine isn't available, and what you should put in yours.
My .source file is a collection of variables and a few functions which I wanted to use over and over again, but:
Did not want to keep retyping
Wanted to be able to change without having to manually edit all of the files
For example, a standard $PATH on Mac OS X might look like this:
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin
However, if you use MacPorts you might want to add /opt/local/bin
PATH=/opt/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin
but what if you then decide that You want to use MacPorts, but you don't want the MacPorts versions to be the default, you might change that to
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/opt/local/bin
or you might decide that you really prefer Fink so now you want
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/sw/bin
PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/sw/bin:/opt/local/bin
and then eventually maybe you find yourself using Homebrew and decide that you don't want either of those things in your $PATH, but you do want to make /usr/local/bin/ the default:
PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin
Now, imagine that every time you changed your mind, you had to look through all of your shell scripts to make sure that you changed them all.
You could do that... or you could do the much more sane thing, and install "global" variables in one master file which gets read by all of your shell scripts, so that if you want to make a change all you have to do is change that master file.
.source is that master file.
If I define my $PATH in ~/.source and then read-in ~/.source to all my scripts, any changes are made automatically.
"That's such an edge case, it hardly seems worth it"
For example, when I have a script which needs to email me, I could do:
but then I have two potential problems.
If I share this script with anyone, they now have my email address. Worse, if I put it online, it's going to get indexed by Google and then by harvested by spambots.
What if change my email address later?
To avoid these problems, when I need to email myself, I use a variable ($MAILTO) like so
foo | Mail -s "Output of foo" "$MAILTO"
In my ~/.source file I include:
and then if I change it later, it's automatically changed everywhere.
If I need to be notified of something immediately, I can use my email-to-SMS address, which I put in a different variable: $SMSTO
which means that I can use that without giving everyone my cell phone number either.
Almost all of my scripts used to include this line:
to get the shortname of the script for use in error and informational messages. Now I just put that in .source and I can use $NAME in all my scripts.
I found that I was often writing functions in my shell scripts which would send a message via growlnotify (which is helpful when scripts run automatically via Hazel or launchd), and which would send the same message via 'echo' (which was helpful when running the script while ssh'd into another machine).
The biggest problem there is that I kept getting the syntax or order of arguments for growlnotify wrong, and it was tedious.
So I wrote a simple bit of script code which:
checks to see if growlnotify is installed
if it is, creates two functions: one named 'msg' which I use instead of 'echo' to send messages both to 'echo' and 'growlnotify', and the other named 'sticky' which will create a 'sticky' Growl message as well as 'echo'
if growlnotify isn't installed, 'msg' and 'sticky' are just other ways of saying 'echo "$NAME: ' plus whatever message I put in
Here's the code which goes in .source:
which growlnotify >/dev/null if [ "$?" = "0" ] then # use growlnotify # IFF we are giving a "sticky" alert, let's use a different icon # to catch the user's attention, since 'sticky' messages # are, I assume, always fairly urgent/important ALERT="$HOME/Dropbox/Dropbox/Public/Growl-App-Icons/YellowAlert.png" # CHANGE THAT PATH to work on your system!!! # you can download the file I use from here: # http://dl.dropbox.com/u/18414/Growl-App-Icons/YellowAlert.jpg msg () { # rather than have two separate functions # one for sticky and one for 'regular' # I decided to combine them. Now for a regular message # I type 'msg FOO' but for a 'sticky' # message, I type 'msg sticky FOO' # which reinforced the idea that it's the same thing as 'msg' # just with more emphasis. # if [ "$1" = "sticky" ] then shift growlnotify -a "$APP" --sticky --image "${ALERT}" --message "$NAME" "$@" else growlnotify -a "$APP" --message "$NAME" "$@" fi echo "$NAME: $@" } # I'm keeping this around, for now, until I go through my scripts # and make sure that none of them are using it anymore. # Eventually this will go away, as well as the 'sticky' in the 'else' clause below sticky () { echo "$NAME: $@" growlnotify -a "$APP" --sticky --image "$ALERT" --message "$@" "$NAME (oldsticky)" } else # if we get here, growlnotify was not found # so 'msg' and 'sticky' msg () { if [ "$1" = "sticky" ] then # We can't really do 'sticky' in echo # but 'tput bel' will cause a 'system beep' # even via ssh (works on OS X, Linux, and OpenBSD. Presumably others) shift tput bel fi echo "$NAME: $@" } sticky () { echo "$NAME: $@" } fi
Now, wherever I would have used:
echo "$NAME: foo bar bah"
and the message is output via echo (with "$NAME: " prefixed) and to growlnotify. It looks something like this:
a TEMP place for my stuff
I try to avoid making temporary files whenever possible, but sometimes it's just much easier. I used to just dump them into /tmp/ but on a shared server, files in /tmp/ might be readable to anyone else on that computer, which is something I wanted to avoid.
A better idea is to use TMPDIR, which is set automatically on Mac OS X (at least in Snow Leopard). However, before I learned about TMPDIR, I already had a bunch of scripts written which used "$TEMP" but since $TEMP was defined in .source I was able to change it fairly easily. I actually made it so that if TMPDIR isn't found, my temporary files will be placed in ~/.Trash/ (the folder where OS X trash is kept before being emptied), and it even makes sure that it exists (which it might not if this is a non-OS-X Unix system). If it doesn't exist, it tries to create it, and if it can't create it, it falls back to /tmp/ as a last resort.
if [ -d "$TMPDIR" ] then TEMP="$TMPDIR" else TEMP=${HOME}/.Trash/ TMPDIR=${HOME}/.Trash/ export TEMP export TMPDIR if [ ! -d "$TEMP" ] then echo "Attempting to make TEMP ($TEMP):" mkdir -p "$TEMP" || TEMP="/tmp" fi fi
My .source file doesn't have very much in it, but it's hugely helpful to have the ability to define my own variables and functions to be used in all of my scripts.
If you try to use one of my scripts and get an error about 'command not found' it is probably 'msg' which you can copy/paste into your own ~/.source file.
"What if I want to override the variable?"
What happens if you have a script where you only want commands from a specific PATH but you have already set a global $PATH in .source? Simple, just define $PATH again after you read .source:
SOURCE="${HOME}/.source" if [ -r "${SOURCE}" ] then . "${SOURCE}" else echo "$0: no ${SOURCE} found" fi PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin
That will tell your script to use the last $PATH variable.
"Isn't it a hassle to include the block of text for .source at the top of each post?"
Not at all. I use TextExpander to create my base shell script template, which I outlined in my next post. It includes that block automatically.
I've been using ~/.source for a long time, even before I used Dropbox. These days, .source is actually a symbolic link:
lrwxr-xr-x 1 luomat 27 Mar 7 20:17 /Users/luomat/.source -> Dropbox/DotFiles/source.txt
Note that I don't use ".source" on Dropbox because I'm not sure how Dropbox handles that, plus it makes it easier to find and edit when needed.
All of my Unix-y configuration files, including my .wgetrc and .zshenv are stored in ~/Dropbox/DotFiles/ so I can use them across all of my computers.
To link my ~/Dropbox/DotFiles/source.txt to ~/.source I simply did this in Terminal:
cd ln -s Dropbox/DotFiles/source.txt .source
and all of the scripts continue to read it just the same as always. Plus I have the benefit of my last 30 days worth of changes being stored on Dropbox in case I change my mind. Sort of like a poor (read: lazy) man's 'version control' system, or "30 day undo."
Short URL for this page: http://luo.ma/dotsource