Directory dependent history and environment, version 2
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, functions 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
, starting with the file closest
to the root of the file system.
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
CDEH_ROOT
should be set system wide, and the two
files do_prompt and env.bashrc
should be installed in CDEH_ROOT
.
I also have addhistory
(see below) installed in
CDEH_ROOT
, and keep a symbolic link from
/usr/local/bin
to it.
The directory $CDEH_ROOT/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 $CDEH_ROOT > ls -l -rwxr-xr-x 1 root root 93 2007-05-25 17:04 addhistory* -rwxr-xr-x 1 root root 3676 2018-02-16 16:31 do_prompt* -rw-r--r-- 1 root root 6477 2018-02-16 17:24 env.bashrc drwxrwxrwt 4 root root 4096 2018-02-14 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 very bottom of your ~/.bashrc
file:
#CDEH_VERBOSE=1 # Set this to get more stuff printed. HISTSIZE=1200 # Example, the default is 1000. CDEH_ROOT=/encrypted/cdeh # Or define this globally. source $CDEH_ROOT/env.bashrc
By picking an encrypted partition for CDEH_ROOT
,
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 PROMPT_COMMAND will now source all env.source
files in your working directory path, starting with file with the shortest
path. For example, if there exist /opt/env.source
,
/opt/projects/cdeh/env.source
and /opt/projects/cdeh/test/env.source
and you cd into the directory /opt/projects/cdeh/test/a/b
then first /opt/env.source
will be sourced, followed
by /opt/projects/cdeh/env.source
and finally /opt/projects/cdeh/test/env.source
.
The small script addhistory should be installed
somewhere in your PATH
, or in CDEH_ROOT
(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 its subdirectories (that do not already have their own history file).
Detailed description
env.bashrc
The following environment variables are set:
CDEH_HISTSIZE
is set toHISTSIZE
or, if that wasn't defined, to 1000. Subsequentially,HISTSIZE
is reset toCDEH_HISTSIZE
every timePROMPT_COMMAND
is executed. Thus, if you wish to use a particularHISTSIZE
, you have to set it before sourcingenv.bashrc
— or, you can changeCDEH_HISTSIZE
afterwards.- If
HISTFILESIZE
isn't already set at the moment thatenv.bashrc
is sourced, or when its value is less thanCDEH_HISTSIZE
, then it is set to the same value asCDEH_HISTSIZE
. CDEH_USER
is 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.CDEH_HISTROOT
is set to the base path for the history files of this user:$CDEH_ROOT/history/$CDEH_USER
.CDEH_TMP
is set to a uniq path in /tmp for temporary files.- A
function
exit
is 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
. - A
function
resource
is defined; you can run this reread (possibly altered)env.source
files without changing directory. - The
functions
no_path
,add_path
,pre_path
anddel_path
are defined which allow you to manipulate colon seperated directory lists likePATH
. - If
TOPPROJECT
is not set, it is set toDEFAULT_TOPPROJECT
. PROMPT_COMMAND
is initialized.CDEH_STORE_ENVIRONMENT
is set to"yes"
so that on the first invocation ofPROMPT_COMMAND
the current environment is saved to$CDEH_TMP/env.base
. This is why sourcingenv.bashrc
must occur at the very end of your~/.bashrc
file.
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 CDEH_TMP
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
CDEH_HISTROOT
doesn'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 ubuntu/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 calling
stat
(package: coreuitls) andawk
(package: gawk), both of which need to be in yourPATH
.
Example: A system for environment switching
/home/carlo/projects/ircproxy
)
I have the project specific env.source
file, and a
env.compiler
file. The latter determines which compiler to use
when this project is the current/main project. Even library project
directories have their own env.compiler
file, because I can
be working on them, without having a relationship with any other particular
project. The env.compiler
file defines the environment variables
CC
, CXX
, CPP
and CXXCPP
.
For example (to use the default compilers),
CC="gcc" CXX="g++" export CC CXXwhile 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-gcc
and ansset-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 a env.compiler
with 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
env.source
that defines several "standard" environment variables and aliases.
- REPOBASE — The projects root directory.
- TOPPROJECT — This environment variable is only set for standalone projects. Library projects usually don't define this.
- GIT_AUTHOR_EMAIL, GIT_AUTHOR_NAME, GIT_COMMITTER_EMAIL, GIT_COMMITTER_NAME — Set to the identity to use for that project.
- 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 version 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./configure
scripts. - 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
doxygen
has 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
grep
all 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)
). - syncwww — Whenever a project contains documentation, the alias
syncwww
is 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.
The following functions are defined:
- configure — usually,
configure () { pushd "$REPOBASE-objdir"; $REPOBASE/configure $CONFIGURE_OPTIONS && echo "Configured with $CONFIGURE_OPTIONS."; popd }
- make — usually,
make () { CURDIR=$(pwd); CPUS=`grep '^processor' /proc/cpuinfo | wc --lines`; not_done=1; while [ $not_done -eq 1 ]; do not_done=0; case $1 in -j) shift; CPUS=$1; shift; not_done=1 ;; maintainer-clean) /usr/bin/make -C $REPOBASE-objdir maintainer-clean; cd $REPOBASE && ./autogen.sh ;; ctags | tags) cd $REPOBASE-objdir && ctags --language-force=C++ --regex-C++='/(^|[[:space:];{])using[[:space:]]+([[:alnum:]_]*)[[:space:]]*=/\2/' `s` ;; *) /usr/bin/make -C $REPOBASE-objdir -j $CPUS $@ ;; esac; done; cd $CURDIR }
As an example, the following is my env.source
file
as used for my cwchessboard project:
export TOPPROJECT=/home/carlo/projects/cwchessboard 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 $TOPPROJECT/cwchessboard/*.cc $TOPPROJECT/cwchessboard/*.h' alias vi='vim -c "set tags=$TOPPROJECT/cwchessboard-objdir/tags"' export CONFIGURE_OPTIONS="--enable-maintainer-mode --enable-debug --prefix=$INSTALL_PREFIX" 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 set TOPPROJECT
, it can
use TOPPROJECT
to change, for instance, the
configure options.
This project also defines the alias syncwww
:
source $TOPPROJECT/env.compiler export GIT_COMMITTER_NAME="Carlo Wood" export GIT_COMMITTER_EMAIL="carlo@alinoe.com" export GIT_AUTHOR_NAME="Carlo Wood" export GIT_AUTHOR_EMAIL="carlo@alinoe.com" export OUTPUT_DIRECTORY="/home/carlo/www" export REPOBASE="/home/carlo/projects/libcwd/libcwd" # 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" if test "$TOPPROJECT" = "/opt/secondlife/viewers"; then if test "$(pwd)" = "$REPOBASE-objdir32"; then INSTALL_PREFIX="/sl/i386-linux-gnu/usr" CXXFLAGS=-m32 else INSTALL_PREFIX="/sl/x86_64-linux-gnu/usr" CXXFLAGS= fi fi CPPFLAGS= LDFLAGS= CFLAGS= 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 $REPOBASE/*.cc $REPOBASE/include/*.h $REPOBASE/include/libcwd/*.h $REPOBASE/include/libcwd/*.inl $REPOBASE/utils/*.cc" alias vi='vim -c "set tags=$REPOBASE-objdir/tags,$REPOBASE-objdir/include/tags,$REPOBASE-objdir/include/libcwd/tags,$REPOBASE-objdir/utils/tags"' alias syncwww='rsync -rltz -e /usr/bin/ssh --delete --exclude-from=$REPOBASE-objdir/documentation/www/exclude --verbose $REPOBASE-objdir/documentation/ libcwd-shell:"~/libcwd.www/htdocs"' export CONFIGURE_OPTIONS="--enable-maintainer-mode --prefix=$INSTALL_PREFIX" # Compiler colorization. add_path /usr/src/crror/crror export GOPATH=~/golang BASENAME=`basename "$TOPPROJECT"` if [ "$BASENAME" == ircproxy -o "$BASENAME" == cppgraph ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-threading" fi if [ "$BASENAME" == viewers ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-nonthreading --disable-alloc --disable-location --enable-optimize" fi if [ "$BASENAME" == slavatar ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-alloc --enable-optimize" fi if [ "$BASENAME" == "speech" ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-location --enable-optimize" fi if [ "$BASENAME" == "Firmware" ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-alloc --disable-optimize" fi if [ "$BASENAME" == "aicxx" ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --enable-optimize" fi if [ "$BASENAME" == "memorymodel" ]; then CONFIGURE_OPTIONS="$CONFIGURE_OPTIONS --disable-alloc --disable-location --enable-optimize" fiWhere the shell functions configure and make are already defined in a parent directory of this project. Of course, these are just ideas. Use your creativity and write env.source files that will make your life easier!