Directory dependent history and environment, version 2

Carlo Wood, Apr 2018

Table of contents

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:

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:

Example: A system for environment switching

  • In project directories (like /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 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-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
    
  • Each projects (root) directory gets an env.source that defines several "standard" environment variables and aliases.

    The following aliases are defined:

    The following functions are defined:

    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"
      fi
    
    Where 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!