Deficiencies of the CD command

Do you realise how many times you type "cd" per day? Do you realise how many times you retype the same directory names again and again? Ever since I migrated from 4DOS/NT shell on Windows to using Bash on Unix platforms, I was missing its "cd" history access.

In 4NT the history of the visited directories can be navigated by Ctrl+PgUp/Dn. Every time you go to a new directory by "cd", its name automaticly goes on top of an easily accessible history list.

In Bash "cd -" switches between the last two directories. This is a function in the right direction but many times I wanted to go to the directory before the last, I dreamed for something like "cd -2".

Brief scripting restores some sanity in the directory navigation of Bash.

Installing the CD history function

To install the modified CD function, copy acd_func.sh to your home directory. At the end of your .bashrc add "source acd_func.sh". Restart your bash session and then type "cd --".

petarm@safe$ cd --
0  ~

Type "cd --" to verify if the installation works. Above you may see the result "0 ~". This shows that you have one directory in your history.

petarm@safe$ cd work
petarm@safe$ cd scripts
petarm@safe$ pwd
/home/petarm/work/scripts
petarm@safe$ cd --
0  ~/work/scripts
1  ~/work
2  ~
petarm@safe$ cd -2
petarm@safe$ pwd
/home/petarm

The "cd" command works as usual. The new feature is the history of the last 10 directories and "cd" expanded to display and access it. "cd --" (or simply pressing ctrl+w) shows the history. In front of every directory name you see number. "cd -num" with the number you want, jumps to the corresponding directory from the history.

How CD with history works

lotzmana@safe$ nl -w2 -s' ' acd_func.sh
  1 # do ". acd_func.sh"
  2 # acd_func 1.0.6, 16-feb-2005
  3 # petar marinov, http://bashrc.sourceforge.net, this is public domain
  4
  5 cd_func ()
  6 {
  7   local x2 the_new_dir adir index
  8   local -i cnt
  9
 10   if [[ $1 ==  "--" ]]; then
 11     dirs -v
 12     return 0
 13   fi
 14
 15   the_new_dir=$1
 16   [[ -z $1 ]] && the_new_dir=$HOME
 17
 18   if [[ ${the_new_dir:0:1} == '-' ]]; then
 19     #
 20     # Extract dir N from dirs
 21     index=${the_new_dir:1}
 22     [[ -z $index ]] && index=1
 23     adir=$(dirs +$index)
 24     [[ -z $adir ]] && return 1
 25     the_new_dir=$adir
 26   fi
 27
 28   #
 29   # '~' has to be substituted by ${HOME}
 30   [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"
 31
 32   #
 33   # Now change to the new dir and add to the top of the stack
 34   pushd "${the_new_dir}" > /dev/null
 35   [[ $? -ne 0 ]] && return 1
 36   the_new_dir=$(pwd)
 37
 38   #
 39   # Trim down everything beyond 11th entry
 40   popd -n +11 2>/dev/null 1>/dev/null
 41
 42   #
 43   # Remove any other occurence of this dir, skipping the top of the stack
 44   for ((cnt=1; cnt <= 10; cnt++)); do
 45     x2=$(dirs +${cnt} 2>/dev/null)
 46     [[ $? -ne 0 ]] && return 0
 47     [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
 48     if [[ "${x2}" == "${the_new_dir}" ]]; then
 49       popd -n +$cnt 2>/dev/null 1>/dev/null
 50       cnt=cnt-1
 51     fi
 52   done
 53
 54   return 0
 55 }
 56
 57 alias cd=cd_func
 58
 59 if [[ $BASH_VERSION > "2.05a" ]]; then
 60   # alt+w shows the menu
 61   bind -x "\"\M-w\":cd_func -- ;"
 62 fi

4-8: cd_func() is a function, variables are declared local and are automaticly deleted at the end of the function

10-13: if the function is called with a parameter "--" then it dumps the current content of the directory history. It is stored in the same place pushd/popd keep names — the directory stack. Storage is the same, access is different.

15-16: Argument $1 is transferred into $the_new_dir for some postprocessing. Immediately after that, if there are no parameters we assume that user asked for his home directory

18-26: If parameter begins with - then user attempts access to one of the names in the history list. $index gets the number of the directory, then into $adir we extract the correspondent name. For example, "dirs +3" dumps directory #3 from the stack.

At this point in $the_new_dir we have either a name specified explicitely as a parameter or a name obtained from the history of previously visited directories.

30: If a directory name begins with ~ then this character has to be replaced by the actual home directory name.

34-36: pushd does the actual CD. It also puts the name on top of the directory stack. stdout is redirected to /dev/null in order to completely imitate how CD works. Notice that any output to stderr, for example a message telling that the directory specified by the user doesn’t exist will show up, which is again similar to what CD does. The function aborts if pushd fails. We also need the new directory name for further analysis and $the_new_dir carries it down the function.

40: Keeping track of more than 10 directories is unproductive. As we have just pushd-ed one on top of the stack we trim down any that fall below 11 names deep.

44-52: In a loop we walk through all the names in the directories stack. Any name that matches the new current directory is eliminated. Again, we have to translate any name from the list which begins with ~ to its format of fully expanded home directory.

57: We assign cd to be cd_func().

59-62: If bash version allowes for macros to be assigned we make ctrl+w to summon the history of visited directories.

This script defines a function. It must be source-d and not executed. This way cd_func() is parsed and stored in the current environment. Try "env" and you must see it after all environment variables.

Back to index