Code snippets, nerd recipes & source cookbook

Go Directory Utility

The idea

Years ago (we are talking early nineties!) I was exposed to a MS/DOS cli replacement called 4DOS. This command line processor had a lot of nice features including support for ANSI colors, enhanced piping, etc. I was accustomed to Unix and AmigaDOS, so nothing really striking, except for one nice thing: it had a variation of the command cd (change directory) that would take a string and instantaneously jump you to a leaf in the directory tree matching that string. And if you issued the command again, it would jump to the next leaf directory in the tree matching that name, in a cycle.

Fast forward years later: my Linux box is messy and packed with all sorts of directory names. Finding the exact position of a folder sometimes requires using the find command, then a cd to the exact path.  It would therefore be very useful to get a gd command, as in “go to directory”, that could jump you instantaneously to the required directory name. I’d like to share it, as it can prove very practical, especially when you have a lot of git projects sitting around, and you don’t want to waste so much time with cd commands going back and forth the directory tree. If you remember the name of the cloned folder, that is enough.

The implementation

The implementation works by creating an index file of all the directories accessible to the user in the directory tree (starting from /), in the format

leaf;/full/path/to/leaf

“leaf” is the name of the last directory in the full path, normalized in lower case for more efficient case insensitive matching.
The index is stored in $HOME/.directory.idx , it will be generated when it is not present or with

$ gd --rescan

The command works by matching the parameter substring passed to it with the leaf part of the index, starting from the first available path in the index (alphabetically ordered) if current path and path in the index matched by leaf do not coincide.
If they do coincide, it will move to the next path matched by leaf. if the matching leaf it he last one in the list it will restart from the first.

How to use it

The code snippet with the gd() function must be sourced in the bash, or put in .bashrc / .bash_profile to execute it every time the bash starts. It has been implemented as a bash function and it has to be executed in the current bash environment. There would bein fact no point in having it executed from an external command, the bash would spawn a child process, and upon ending every change to the current working directory would be lost anyway.
The syntax is very simple:

$ gd [sub]string_of_directory_name

et voilá, you get teleported to the directory name. This will work also on MacOS, albeit a bit slower than on Linux, especially during the rescan phase. Hope this can be useful to someone else!

GD Example
A feel of gd on Linux
A feel of gd on MacOS
 
#!/bin/bash

function gd()
{
  local indexFile="$HOME/.directory.idx"

  if [ $# -eq 0 ]; then
      echo "Go Directory usage:"
      echo
      echo "gd [prints this help]"
      echo "gd --rescan [rebuilds the index]"
      echo "gd dir [cycle jumps to the next  directory containing the" 
      echo "        string \"dir\" in the last part of the path]"
      echo
      return 
  fi
  if [ "$1" == "--rescan" ]; then
    rm $indexFile
  fi
  if [ -f $indexFile ] ; then
    local match=`echo -n $1 | tr "[:upper:]" "[:lower:]"` 
    local idx=1
    local cwd=`pwd`
    shopt -s nocasematch
    if [[ "$cwd" =~ ^.*$match.*$ ]]; then
      local lines=`grep -n -e "^.*$match.*;\/" $indexFile`
      local n=`echo "$lines" | wc -l`
      idx=`echo "$lines" | grep -n "$cwd" | cut -d ":" -f 1 | head -n 1`
      let idx=idx+1
      if [ $idx -gt $n ]; then
        idx=1
      fi
    fi
    local entry=`grep -n -e "^.*$match.*;\/" $indexFile | head -n $idx | tail -n 1`
    local path=`echo $entry | cut -d ":" -f 2 | cut -d ";" -f 2`
    if [ -n "$path" ]; then
      cd "$path"
    else
      echo "No directory leaf contains \"$match\" in the index. Try with a rescan first." > /dev/stderr
      return -1
    fi
  else
    echo "Building directory index..."
    find / -type d 2>/dev/null | sort > $indexFile.tmp
    cat $indexFile.tmp | awk '{ FS="/";  print tolower($NF) ";" $0 }' > $indexFile
    rm $indexFile.tmp
  fi
  shopt -u nocasematch 
}

You can clone the project from github and add it to your .bashrc or .bash_profile, in aternative you can source it with

#!/bin/bash
...

. /path/to/godirectory

...

inside your .bashrc or .bash_profile.

This has been tested on Fedora 25, Ubuntu 16.04, OS/X El Capitan¹. It should work seamlessly on every posix system with bash 4+.

Notes:
¹ On OSX I have installed bash 4.4  (and the gnu coreutils, but it should not be mandatory) with the brew package manager.
² This is released under the GPL 3.0, please refer to the github repository for the licence details.