Learning Unix is to a large extent a long initiation process. One of the first things one learns is basic shell scripting commands, followed by the customization of one shell. When I started working with Unix somewhere in 1993, we used Sun stations, and the default shell was cshrc. My first .login and .cshrc were not built from scratch, instead they contained bits and pieces gathered from other students and teaching assistants. The university not only had sun station, but a bunch of Macintoshes running AU/X, Apple’s Unix before OS X. As they shared the same NFS file-system, writing cross-plateform files was needed. My configuration files were something that always kept, adapting them to new systems, rewriting parts, porting them to new shells, first tcsh, then bash.
While I rely less on NFS, I increasingly use unison for synchronization, so large parts of my home directory are shared between many machines. This is convenient as I can devellop a project on my Mac laptop and run it on the Solaris workstations or our Linux cluster. Of course this means handling the idiosyncrasies of each plateform. In this post, I will try to explain how I handle the different parts of configuration. Of course, this code is tailored for my own usage, use them it at your own risk.
What the configuration files aims to do is to define the different paths in a rational way for each plateform. They do not ensure that all plateform behave the same, more that all the possible tools are accessible.
Some structure
The first thing when sychronizing things among multiple plateforms is to have some structure in your files. This applies in particular to files in my home directory. I use the Mac OS X structure, i.e my home directory contains the following directories:
- Documents
- Library
- Projects
- Pictures
There are other directories, but those are the ones that are shared among all machines. In order to avoid problems I don’t synchronize the whole home directory, instead those four directores are synchronized totally in parts (no point in synchronizing Library/Caches
for instance.
On systems where I have no root access, or where I don’t want to modify the installed programs, there is a personal usr hierarchy, i.e ~/usr/lib
, ~/usr/bin
, etc. This hierarchy should be included in the path if present.
One important directory is Library/bin that contains all my shell scripts, and the configuration files for the shell, called bashrc.bash
and profile.bash
. This directory also contains my own personal shell script commands, either in the basic directory if they work on all plateforms, or in plateform specific directories, i.e Darwin
, Solaris
, Linux
.
The actual .bashrc
and .profile
file source those files. For instance, the .bashrc
file on the Mac laptop contains this code:
if test -z "$ALREADY_SOURCED_RC" then . ~/Library/bin/profile.bash fi . ~/Library/bin/bashrc.bash
The profile.bash file
The meat of the configuration is contained in the ~/Library/bin/profile.bash
file. The first line of the file defines the variable, so the whole setup is only done onces.
export ALREADY_SOURCED_RC="true"
The next step is to define some functions to manipulate paths, as we are going to do a lot of this. Those were shamelessly lifted from the bash default configuration files. What those function do is add some directory to some path, either in front or at the end, those function ensure that each directory only appears once in the path.
## Path remove only works for single component paths pathremove () { local IFS=':' local NEWPATH local DIR local PATHVARIABLE=${2:-PATH} for DIR in ${!PATHVARIABLE} ; do if [ "$DIR" != "$1" ] ; then NEWPATH=${NEWPATH:+$NEWPATH:}$DIR fi done export $PATHVARIABLE="$NEWPATH" } pathprepend () { pathremove $1 $2 local PATHVARIABLE=${2:-PATH} export $PATHVARIABLE="$1${!PATHVARIABLE:+:${!PATHVARIABLE}}" } pathappend () { pathremove $1 $2 local PATHVARIABLE=${2:-PATH} export $PATHVARIABLE="${!PATHVARIABLE:+${!PATHVARIABLE}:}$1" }
Now that we have those basic functions, we can start setting things up. First, we define two basic variables, one contains the general OS name (Darwin, Solaris, Linux), the other defining where all the configuration is (~/Library/bin
).
export GENERALOSTYPE=`uname` export HOMELIB=$HOME/Library/bin
For each operating system, I can do some special configuration. For instance, many of the Solaris don’t have the latest Java JDK as default, so we define a variable containing the position of the JDK. I also redefine LANG, as some of the Sun station are set up with japanese as default and do not support en_US.UTF-8
.
if [ "$GENERALOSTYPE" == "SunOS" ]; then export LANG="C" export JAVAHOME=/app/jdk1.5.0_07 fi
Now, one of the first things we whish to do is setup a rational java. The default on the Mac laptop is OK, but on Linux, it is not the Sun JDK, and on Solaris it is not the lastest version. So we first look if there is an installation in a rational place, and define JAVAHOME
in consequence, if we have in some way discovered a value for JAVAHOME
, we prepend it to the PATH and the MANPATH (we want to use those settings in preference to the default ones).
# Check for Sun Java installation if [ -d /usr/java/j2sdk ]; then export JAVAHOME=/usr/java/j2sdk fi if [ -n $JAVAHOME ]; then pathprepend $JAVAHOME/bin PATH pathprepend $JAVAHOME/man MANPATH fi
The next step is to setup X11 programs. We basically do the same trick, first looking at reasonable places and then seting up the variables.
# Check for X11 installation if [ -d /usr/X11R6 ]; then export X11HOME=/usr/X11R6 fi if [ -d /usr/local/X11R6 ]; then export X11HOME=/usr/local/X11R6 fi if [ -n $X11HOME ]; then pathappend $X11HOME/bin PATH pathappend $X11HOME/man MANPATH fi
The next step is to setup open-windows programs, they are not really cutting edge, but on Solaris, they can come in handy. Those paths also include certain utilities like xterm
. The detection is done in one go, as I have not seen any cases where they are installed in a different path.
# Check if openwindow is installed if [ -d /usr/openwin ]; then export OPENWINROOT=/usr/openwin pathappend $OPENWINROOT/bin PATH pathappend $OPENWINROOT/man MANPATH pathappend $OPENWINROOT/share/man MANPATH fi # Check if dt in installed if [ -d /usr/dt ]; then export DTROOT=/usr/dt pathappend $DTROOT/bin PATH pathappend $DTROOT/man: MANPATH pathappend $DTROOT/share/man MANPATH fi
Next step is to check for an /opt/local hierarchy. Certain package managers link fink or darwinport install files there. The JAIST solaris installation also has programs there. As they might contain more advanced programs than the default, we prepend the paths. Notice that this time, we setup more paths.
Multiple additions to the PATH must be done separatly, because pathprepend does not handle prepending multiple paths in one operation correctly.
# Check if there is an /opt/local hierarchy if [ -d /opt/local ]; then export OPTROOT=/opt/local pathprepend $OPTROOT/share/info INFOPATH pathprepend $OPTROOT/man MANPATH pathprepend $OPTROOT/share/man MANPATH pathprepend $OPTROOT/bin PATH pathprepend $OPTROOT/sbin PATH fi
Next is the /pkg/all
hiearchy, this seems to be Sun specific.
# Check for /pack/all hierarchy if [ -d /pkg/all ]; then export PACKROOT=/pkg/all pathappend $PACKROOT/info INFOPATH pathappend $PACKROOT/man MANPATH pathappend $PACKROOT/bin PATH fi
Next, we check for the /usr
hiearchy. This is included in the PATH by default by Linux and OS X, but not Solaris.
# Check for /usr hierarchy if [ -d /usr ]; then pathappend /usr/man MANPATH pathappend /usr/share/man MANPATH pathappend /usr/bin PATH pathappend /usr/local/bin PATH pathappend /usr/sbin PATH fi
Next, we check for the Developer tools, this one is Apple specific.
#Check for Apple developer tools if [ -d /Developer/Tools ]; then pathappend /Developer/Tools PATH fi
Now we check if there is a usr
hierarchy in the home directory.
# Check for usr hierarchy in home directory if [ -d "$HOME/usr/" ]; then pathprepend $HOME/usr/bin PATH pathprepend $HOME/usr/man MANPATH fi
Finally, we prepend ~/Library/bin
to the path.
# Finish with commands in Library/bin directory # Plain scripts are stored in ~/Library/bin pathprepend $HOMELIB PATH
We then check if there is a plateform specific directory.
# If there is a subdiretory matching the architecture, load this one. if [ -d "$HOMELIB/$GENERALOSTYPE" ]; then pathprepend $HOMELIB/$GENERALOSTYPE PATH fi
We finish by setting up general variables, like the PAGER, we always check if variables are defined first, as they might already be defined by other sources, because of a remote login, or for instance in the ~/.MacOSX/environment.plist
file.
#Set up X11 display if [ -z $DISPLAY ]; then export DISPLAY=":0.0" fi # Setup screen if [ -z $SCREENRC ]; then export SCREENRC=$HOMELIB/screenrc fi #Setup langage / encoding if [ -z $LANG ]; then export LANG="en_US.UTF-8" fi # Set up prompt if [ "$TERM" == "screen" ]; then export PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}:${WINDOW}\007"' else export PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}\007"' fi
Notice that the screenrc file is also in ~/Library/bin/
so it is synchronized, but as it has no x
bit, it is not a command.
Aliases are defined in separate files that are loaded by the profile file. There are basically three sources for aliases: the general aliases.bash
file, the plateform specific one, and finally, some custom script to build aliases out of GUI programs on Mac OS X, that I lifted from the macosxhints web site.
if [ -d "$HOMELIB/$GENERALOSTYPE/osaliases" ]; then if [ -f "$HOMELIB/$GENERALOSTYPE/buildosaliases.bash" ]; then source $HOMELIB/$GENERALOSTYPE/buildosaliases.bash fi for dir in $HOMELIB/$GENERALOSTYPE/osaliases/* do source $dir done fi # Load general aliases if [ -f "$HOMELIB/aliases.bash" ]; then source $HOMELIB/aliases.bash fi # Load plateform specific aliases if [ -f "$HOMELIB/$GENERALOSTYPE/aliases.bash" ]; then source $HOMELIB/$GENERALOSTYPE/aliases.bash fi
Finally, we do a little bit of fiddling with the TERM variable, the problem is in certain cases, the host does not know about the terminal. For instance, the Mac OS X terminal announces itself as xterm-color
, for which Solaris has no clue. We check this by launching terminfo and checking if there is an error, the terminal is not supported, and we fall back to xterm
.
# If terminfo acts up, fall back to xterm terminfo &> /dev/null if [ $? != 127 ]; then export TERM=xterm fi
Finally, we set up completion, I have a special script for unison, and we check for reasonable places on the host machine.
#Completions complete -C $HOMELIB/unison_completions unison if [ -f /etc/bash_completion ]; then . /etc/bash_completion fi if [ -f /opt/local/etc/bash_completion ]; then . /opt/local/etc/bash_completion fi
That is all, while those files are quite long, they are no overly complex. Tell me what you think.