...making Linux just a little more fun!

<-- prev | next -->

History of visited directories in BASH

By Petar Marinov

Deficiencies of the CD command

Do you realize how many times you type cd per day? Do you realize 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've missed its cd history access. In 4DOS/NT 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 automatically 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 of something like cd -2.

A little scripting creates some sanity in the directory navigation of Bash.

Installing the CD history function

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

lotzmana@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.

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

The cd command works as usual. The new feature is the history of the last 10 directories and the cd command expanded to display and access it. cd -- (or simply pressing ctrl+w) shows the history. In front of every directory name you see a 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.5, 10-nov-2004
 3 # petar marinov, http:/geocities.com/h2428, this is public domain

 4 cd_func ()
 5 {
 6   local x2 the_new_dir adir index
 7   local -i cnt

 8   if [[ $1 ==  "--" ]]; then
 9     dirs -v
10     return 0
11   fi

12   the_new_dir=$1
13   [[ -z $1 ]] && the_new_dir=$HOME

14   if [[ ${the_new_dir:0:1} == '-' ]]; then
15     #
16     # Extract dir N from dirs
17     index=${the_new_dir:1}
18     [[ -z $index ]] && index=1
19     adir=$(dirs +$index)
20     [[ -z $adir ]] && return 1
21     the_new_dir=$adir
22   fi

23   #
24   # '~' has to be substituted by ${HOME}
25   [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"

26   #
27   # Now change to the new dir and add to the top of the stack
28   pushd "${the_new_dir}" > /dev/null
29   [[ $? -ne 0 ]] && return 1
30   the_new_dir=$(pwd)

31   #
32   # Trim down everything beyond 11th entry
33   popd -n +11 2>/dev/null 1>/dev/null

34   #
35   # Remove any other occurence of this dir, skipping the top of the stack
36   for ((cnt=1; cnt <= 10; cnt++)); do
37     x2=$(dirs +${cnt} 2>/dev/null)
38     [[ $? -ne 0 ]] && return 0
39     [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
40     if [[ "${x2}" == "${the_new_dir}" ]]; then
41       popd -n +$cnt 2>/dev/null 1>/dev/null
42       cnt=cnt-1
43     fi
44   done

45   return 0
46 }

47 alias cd=cd_func

48 if [[ $BASH_VERSION > "2.05a" ]]; then
49   # ctrl+w shows the menu
50   bind -x "\"\C-w\":cd_func -- ;"
51 fi

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

8-11: 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.

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

14-22: If parameter begins with '-' then the user is attempting to access one of the names in the history list. $index gets the number of the directory, then we extract the corresponding name into $adir. For example, dirs +3 dumps directory #3 from the stack.

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

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

26-30: 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.

31-33: Keeping track of more than 10 directories is unproductive. Since we have just pushed one on top of the stack, we trim off any that fall below 11 names deep.

34-44: We loop through all the names in the directory 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.

47: We assign cd to be cd_func().

48-51: If the bash version allows for macros to be assigned we make ctrl+w summon the history of visited directories.

This script defines a function. It must be sourced and not executed, so that cd_func() is parsed and stored in the current environment. Try env and you must see it after all environment variables.

Documentation page of the script

Visit the acd_func.sh man page.


For comments on this article please visit or join zepp mailing list.
The text of this page is public domain.

 


Copyright © 2004, Petar Marinov. Released under the Open Publication license

Published in Issue 109 of Linux Gazette, December 2004

<-- prev | next -->
Tux