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!
#!/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, and made bash the default shell (I don’t particularly like zsh).
² This is released under the GPL 3.0, please refer to the github repository for the licence details.