Directory dependent history and environment
Table of contents
- Introduction
- How does it work?
- Downloading and setting up cdeh
- Detailed description
- Example: A system for environment switching
Introduction
This page describes how to set up cdeh, a set of scripts to automatically switch history and environment (environment variables and aliases) as function of the directory (tree) that you are in.
If, like me, you like working from the command line, and you
are using bash as your SHELL, then this is for you.
The type of commands someone gives often depend on the current working directory. Going up through the command line history, it is annoying having to search for the last time you gave that particular command when, in fact, it was probably the last command you gave in a terminal window on that prompt. Also, if you close a terminal, open a new one and change directory back to where you were - you don't want to suddenly have your command history cluttered with unrelated commands.
Especially when you are a developer who works from the command line,
like me, it turns out to be very productive to automatically switch
environment variables and aliases as function of the project directory
that you are in. This makes it possible to create simple aliases for
repetive commands that are nearly the same in every project.
For example, I configure every project by typing
configure; the real command that is executed then depends
on the directory I am in. If I am in
~/projects/edragon/edragon-objdir then the actual command is
../edragon/configure --enable-maintainer-mode --enable-debug --prefix=/usr/local/install/4.1.2-20061115 --disable-pch,
while if I am in ~/projects/libecc/libecc-objdir then
the actual command is
../libecc/configure --enable-maintainer-mode --enable-optimization --enable-debug --prefix=/usr/local/install/4.1.2-20061115.
Because these commands seldom change while in the same directory, it is
convenient to have a simple alias for them that is a function of the
directory that I am in.
How does it work?
The switching is linked to the command prompt: whenever a command line
prompt is displayed, the current directory is checked and history/environment
switching is performed when necessary. This is achieved through the
use of the bash environment variable PROMPT_COMMAND.
The actual history switching is done by changing the bash environment
variable that controls the history: HISTFILE.
You are free to use the other HIST*
environment variables (man bash). The environment variables
HISTSIZE and HISTFILESIZE are also used, but
if you set them prior to initialization of cdeh, then their values are
used.
The environment is changed by sourcing any files in your directory tree
that have the name env.source.
These files may contain whatever you want.
Personally, I use a system to facilitate different C++ projects in different
directories, dictating some mandatory content of env.source,
but that is basically independent of how cdeh works and is therefore
described seperately at the end of this document.
Downloading and setting up cdeh
The cdeh system consists of three system wide files, although it is possible to install them in your home directory if you don't have root.
There are three system wide files. Normally, the environment variable
CDEHROOT should be set system wide, and the two
files do_prompt and env.bashrc
should be installed in CDEHROOT.
I also have addhistory (see below) installed in
CDEHROOT, and keep a symbolic link from
/usr/local/bin to it.
The directory $CDEHROOT/history should exist,
be owned by root and mode 1777. If you don't have root, then
it should exist, be owned by you and be mode 700.
For example,
> cd $CDEHROOT > ls -l -rwxr-xr-x 1 root staff 93 2007-04-18 17:04 addhistory* -rwxr-xr-x 1 root staff 3058 2007-04-18 16:31 do_prompt* -rw-r--r-- 1 root staff 2200 2007-04-18 17:24 env.bashrc drwxrwxrwt 4 root root 4096 2007-04-18 15:52 history/
The latter needs to be sourced from, for example,
your ~/.bashrc file. You can do this by adding the following
line at the bottom of your ~/.bashrc file:
HISTSIZE=1200 # Example, the default is 1000. CDEHROOT=/encrypted/cdeh # Or define this globally. . $CDEHROOT/env.bashrc
Note the leading dot (.).
By picking an encrypted partition for CDEHROOT,
all history files will be encrypted. This is the reason that the
history files aren't stored in the users home directory by default
(which might not be encrypted).
The script do_prompt sources env.source
files in the highest directory that one is in. Thus, in any case
you will need such a file in the root: /env.source.
For now you can just touch it to create an empty file. See below
for a practical example.
The small script addhistory should be installed
somewhere in your PATH or in CDEHROOT
(to keep everything together), using for example a symlink from
/usr/local/bin.
Running addhistory inside any
directory will start a new history file for that directory and all
of it's subdirectories (that do not already have their own history file).
Detailed description
env.bashrc
The following environment variables are set:
CDEHHISTSIZEis set toHISTSIZEor, if that wasn't defined, to 1000. Subsequentially,HISTSIZEis reset toCDEHHISTSIZEevery timePROMPT_COMMANDis executed. Thus, if you wish to use a particularHISTSIZE, you have to set it before sourcingenv.bashrc— or, you can changeCDEHHISTSIZEafterwards.- If
HISTFILESIZEisn't already set at the moment thatenv.bashrcis sourced, or when its value is less thanCDEHHISTSIZE, then it is set to the same value asCDEHHISTSIZE. CDEHUSERis set to"`/usr/bin/whoami`". It should be possible to use this as a directory name. It is used in the path of other files in order to allow the use of this system by more than one user. The reason that those files aren't stored in the users home directories is that I want to allow the use of an encrypted partition for the history files, and the users home directory doesn't need to be encrypted.CDEHHISTROOTis set to the base path for the history files of this user:$CDEHROOT/history/$CDEHUSER.CDEHTMPis set to a uniq path in /tmp for temporary files.PROMPT_COMMANDis initialized.- A
functionexitis defined, overriding the normal 'exit'. This is done to assure that the history is written when you exit the shell, and to cleanup temporary files from/tmp.
do_prompt
This is the main script, executed from PROMPT_COMMAND.
There is little to edit in this file, although you might need to be
aware of the fact that it starts with setting CDEHTMP
again. This should be the same path as is being used in
env.bashrc of course.
Nevertheless, the following points probably need your attention:
- In case the directory
CDEHHISTROOTdoesn't exist, the script will print (every prompt) a warning. Since cdeh only runs every prompt and doesn't keep a record of what warnings it sent before, you might get error or warning messages every prompt. Don't let that scare you off; just fix the problem. - This script also tries to change the title of the current terminal
window into something that reflects both, the current working directory
as well as the history root path.
It does this by calling
xtermset, so you want to have that installed (it is probably part of the package xtermset. On debian:apt-get install xtermset). - In order not to be confused by symbolic links, the script uses
the inodes of directories to know if it changed directory or not.
These inodes are retieved by called
stat(package: coreuitls) andawk(package: gawk), both of which need to be in yourPATH.
Example: A system for environment switching
Whenever you change directory, CDEH will
descent the directories of the current working directory looking for
the first env.source file and source it. For example,
if there exists /env.source,
/home/carlo/env.source and
/home/carlo/projects/libecc/env.source, then
changing directory to /home/carlo will source
/home/carlo/env.source while changing directory to
/home/carlo/projects/libecc/libecc/src will
source /home/carlo/projects/libecc/env.source, instead.
Personally, I use the following system for my different C++ projects.
- In the root (/) I keep this
env.source, needed to undo the effects of any otherenv.sourcethat I use. After all, when you leave a directory then you want specific environment settings for that directory to disappear again! Don't forget to setDEFAULTPATH, see the next item, or yourPATHwill become empty and you cannot execute any commands anymore without giving the full path (hint: /usr/bin/vim ~/.bashrc): - In my
.bashrcfile I setDEFAULTPATHto whatever I want to use when I am in an arbitrary directory that doesn't set it's ownPATH:export DEFAULTPATH="$HOME/bin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/X11R6/bin:/usr/X11R6/bin:$PROTODIR/scripts:/usr/games" export PATH="$DEFAULTPATH"
I also setDEFAULT_TOPPROJECTto the project directory that I want to be the default project when I open a new terminal window (TOPPROJECTis the current project that I am working on and determines the compiler version to use for library projects),export DEFAULT_TOPPROJECT=/home/carlo/projects/ircproxy
- In project directories (like
/home/carlo/projects/ircproxy) I then have the project specificenv.sourcefile, and aenv.compilerfile. The latter determines which compiler to use when this project is the current/main project. Even library project directories have their ownenv.compilerfile, because I can be working on them, without having a relationship with any other particular project. Theenv.compilerfile defines the environment variablesCC,CXX,CPPandCXXCPP. For example (to use the default compilers),CC="gcc" CXX="g++" export CC CXX
while in another project I use a more complex definition that uses two PCs, doing distributed compilation (which, unfortunately doesn't really speed up the compilation process with only two PC's):CC="pcc" CXX="pc++" CC0="/usr/bin/gcc" CXX0="/usr/bin/g++" CC1="/home/carlo/bin/ansset-gcc" CXX1="/home/carlo/bin/ansset-g++" CC2="/usr/bin/gcc" CXX2="/usr/bin/g++" CC3="/home/carlo/bin/ansset-gcc" CXX3="/home/carlo/bin/ansset-g++" export CC CXX CC0 CXX0 CC1 CXX1 CC2 CXX2 CC3 CXX3
Note that
ansset-gccandansset-g++are scripts that further do work to actually use the compiler on another machine (but that is not relevant for this document).I have a lot of different compiler versions installed in
/usr/local. Some projects use a particular version (for example for testing libraries), which could need aenv.compilerwith the following content:CC=gcc-4.0.3 CXX=g++-4.0.3 CPP="/usr/local/$CC/bin/cpp" CXXCPP="/usr/local/$CC/bin/cpp -x c++" export CC CXX export CPP CXXCPP
- Each project (root) directory gets an
env.sourcethat defines several "standard" environment variables and aliases.- TOPPROJECT — This environment variable is only set for standalone projects. Library projects usually don't define this.
- CFLAGS, CXXFLAGS, CPPFLAGS, LDFLAGS — Whatever is appropriate to use for this project. These environment variables are usually used by configure and/or the Makefile.
- CCACHE_DIR — All my non-standard compilers (installed with
--prefix=/usr/local/gcc-$COMPILER_VERSION) are actually scripts, which use ccache when this environment variable is set. - INSTALL_PREFIX — The install prefix used to configure the project.
This is dependend on the compiler version, so that libraries and applications
compiled with a specific compiler versions will only use eachother (and
whatever is installed as part of the Operating System when the library is
not available in
INSTALL_PREFIX). - PKG_CONFIG_PATH — Used by
pkg-config(1), used in many./configurescripts. - PATH — Updated to include the
INSTALL_PREFIX. - LD_LIBRARY_PATH — Updated to include the
INSTALL_PREFIX. - CONFIGURE_OPTIONS — The default configure options to use for this project.
- OUTPUT_DIRECTORY — Where
doxygenhas to write the documentation to. - CTAGS_ROOT_SRCDIRS — Additions source dirs for ctags that this project should use. This is being used by some (standarized) entry in my Makefiles.
The following aliases are defined:
- s — This should print all source files of the project. It is
mainly used from the command line to
grepall sources. For example, to list all source files that use some class nameServerSession, I'd type:grep -l ServerSession `s`without being bothered by where all the source files of this particular project are. - vi — This alias is redefined in order to tell vim where the tag
files for this particular project can be found
(as generated with
ctags(1)). - configure — As explained before, I use this alias to configure a project. It has to be executed from the object directory.
- syncwww — Whenever a project contains documentation, the alias
syncwwwis defined to whatever command needs to be executed to export this documentation, synchronizing the documentation of the current project with the external (public) web pages. Again, not bothering me anymore with project specific details.
As an example, the following is my
env.sourcefile as used for my cwchessboard project:export TOPPROJECT=/home/carlo/projects/cwchessboard source /env.source source $TOPPROJECT/env.compiler CPPFLAGS= LDFLAGS= CFLAGS=-g3 CXXFLAGS=-g3 export CPPFLAGS LDFLAGS CFLAGS CXXFLAGS # These two are helper variables. GCCVER=`$CXX -v 2>&1 | grep '^gcc[ -][Vv]ersion' | sed -e 's/gcc[ -][Vv]ersion //' -e 's/ (.*//' -e 's/ /-/g'` INSTALL_PREFIX="/usr/local/install/$GCCVER" PKG_CONFIG_PATH="$INSTALL_PREFIX/lib/pkgconfig" del_path . pre_path "$INSTALL_PREFIX/bin" pre_path . pre_path "$INSTALL_PREFIX/lib" LD_LIBRARY_PATH export PKG_CONFIG_PATH PATH LD_LIBRARY_PATH INSTALL_PREFIX alias s='ls /home/carlo/projects/cwchessboard/cwchessboard/*.cc /home/carlo/projects/cwchessboard/cwchessboard/*.h' alias vi='vim -c "set tags=/home/carlo/projects/cwchessboard/cwchessboard-objdir/tags"' export CONFIGURE_OPTIONS="--enable-maintainer-mode --enable-debug --prefix=$INSTALL_PREFIX" alias configure='../cwchessboard/configure $CONFIGURE_OPTIONS' export OUTPUT_DIRECTORY=/home/carlo/www export CTAGS_ROOT_SRCDIRS="/usr/src/gtk/glib-current /usr/src/gtk/gtk+-current"
While a C++ library project, like
libcwd, does not setTOPPROJECT, it can useTOPPROJECTto change, for instance, the configure options. This project also defines the aliassyncwww:source /env.source source $TOPPROJECT/env.compiler # These two are helper variables. GCCVER=`$CXX -v 2>&1 | grep '^gcc[ -][Vv]ersion' | sed -e 's/gcc[ -][Vv]ersion //' -e 's/ (.*//' -e 's/ /-/g'` INSTALL_PREFIX="/usr/local/install/$GCCVER" CPPFLAGS= LDFLAGS= CFLAGS= CXXFLAGS= export CPPFLAGS LDFLAGS CFLAGS CXXFLAGS PKG_CONFIG_PATH="$INSTALL_PREFIX/lib/pkgconfig" pre_path "$INSTALL_PREFIX/bin" pre_path "$INSTALL_PREFIX/lib" LD_LIBRARY_PATH export PKG_CONFIG_PATH PATH LD_LIBRARY_PATH alias s="ls /home/carlo/projects/libcwd/libcwd/*.cc /home/carlo/projects/libcwd/libcwd/include/*.h /home/carlo/projects/libcwd/libcwd/include/libcwd/*.h /home/carlo/projects/libcwd/libcwd/include/libcwd/*.inl /home/carlo/projects/libcwd/libcwd/utils/*.cc" alias vi='vim -c "set tags=/home/carlo/projects/libcwd/libcwd-objdir/tags,/home/carlo/projects/libcwd/libcwd-objdir/include/tags,/home/carlo/projects/libcwd/libcwd-objdir/include/libcwd/tags,/home/carlo/projects/libcwd/libcwd-objdir/utils/tags"' alias syncwww='rsync -rltz -e /usr/bin/ssh --delete --exclude-from=/home/carlo/projects/libcwd/libcwd-objdir/documentation/www/exclude --verbose /home/carlo/projects/libcwd/libcwd-objdir/documentation/ libcwd-shell:"~/libcwd.www/htdocs"' export CONFIGURE_OPTIONS="--enable-maintainer-mode --prefix=$INSTALL_PREFIX" alias configure='../libcwd/configure $CONFIGURE_OPTIONS' BASENAME=`basename "$TOPPROJECT"` if [ "$BASENAME" == ircproxy -o "$BASENAME" == cppgraph ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-threading" fi