an9wer

Faster Directory Jumps Using globstar

posted:

2019-08-21

Jumping around directories in a Unix-like system can be achieved using the command cd. It's especially convenient when combined with shell's tab-completion feature, and shell's environment variables such as "$OLDPWD" (which refers to the previous working directory and can also be accessed using cd -) and "$HOME" (which refers to the home directory and can also be accessed using cd ~, or cd - that is with no arguments). However, it becomes inefficient when frequent navigatation is required, especially between directories that are deeply nested.

Then, it comes with some tools to address this issue by track cd history and providing an faster way to jump between frequently visited locations. One such tool is called "z", a lightweight shell script of about 200 lines. It's incredibly simple, yet fairly powerful. I have been using it for a while. It definitely streamlines my workflow. However, sometimes it doesn't work as expected - takes me to the wrong directories. For instance, executing the command z foo navigates to the highest-ranked directory matching the keyword "foo". If there are two such directories - say, fooA and fooB - and fooA ranks higher than fooB. In that case, z foo will take me to fooA. But what if I actually want to go to fooB instead? It seems theres's no way for z to do that.

Then, it turned out that I made my own wheels - a shell script called "cdf" (short for Change Directory From), which is inspired by Vim's file-searching function (check out :h file-searching in Vim). That is the pattern "**" can match directories in any depth. In shell, it has a similar function by enabling the option "globstar", and instead of directories, it can even match files. Here is the script:

shopt -s globstar
cdf() {
        declare -a dirs
        declare -i pick

        # collect all matched directories
        for dir in "$@"; do
                if [[ -d $dir ]]; then
                        dirs+=("$dir")
                fi
        done

        if (( ${#dirs[@]} == 0 )); then
                echo "No matched directories"
        elif (( ${#dirs[@]} == 1 )); then
                cd "${dirs[0]}"
        else
                # make a pick from the list
                for idx in "${!dirs[@]}"; do
                        printf "%s.\t%s\n" "$(( $idx + 1))" "${dirs[$idx]}"
                done
                while true; do
                        read -e -r -p "What is your pick? (1-${#dirs[@]}) " pick
                        if (( $pick <= 0 || $pick > ${#dirs[@]} )); then
                                echo "invalid input - please select from 1-${#dirs[@]}"
                        else
                                cd "${dirs[$pick - 1]}"
                                break
                        fi
                done
        fi
}

To use the script, save it to a file, say "cdf.sh", and load the script through the command source cdf.sh, then the command cdf can be called from shell. For example:

The only drawback of this script is that it can be slow when searching through directories with a large number of files. Other than that, it is straightforward to use and, most importantly, it shows precise matching directories before jumping into one.

Thanks for reading :)

Further Readings