## 31.10. cd by Directory Initials

Here's a handy shell function called c for people who cd all over the filesystem. (I first saw Marc Brumlik's posting of it on Usenet years ago, as a C shell alias. He and I have both made some changes to it since then.) This function is great for shells that don't have filename completion (Section 28.6). This function works a bit like filename completion, but it's faster because the "initials" match only directories and you don't have to press TAB or ESC after each part of the pathname. Instead, you just type the initials (first letter or more) of each directory in the pathname. Start at the root directory. Put a dot (.) after each part.

Here are three examples. The first one shows that there's no subdirectory of root whose name starts with q. The second one matches the directory /usr/include/hsfs and cds there:

>     $ c q.
>     c: no match for /q*/.
>     $ c u.i.h.
>     /usr/include/hsfs/.
>     $

In the next example, trying to change to /usr/include/pascal the abbreviations aren't unique the first time. The function shows me all the matches; the second time, I add another letter ("a") to make the name unique:

>     $ c u.i.p.
>     c: too many matches for u.i.p.:
>     /usr/include/pascal/. /usr/include/pixrect/. /usr/include/protocols/.
>     $ c u.i.pa.
>     /usr/include/pascal/.
>     $

Go to http://examples.oreilly.com/upt3 for more information on: c.csh, c.sh

The Bourne shell function is straightforward; it's shown below.[98] The C shell alias needs some trickery, and there are two versions of it: one if you already have an alias for cd and another if you don't. (The C shell if used in the c alias won't work with a cd alias. Although the csh manual page admits it won't work, I'd call that another C shell bug.)

> [98]You may need to remove the `function` keyword in older Bourne shells, but it is required for bash.

`set` Section 35.25, `$#` Section 35.20

>     function c( )
>     {
>        dir="$1"

>        # Delete dots.  Surround every letter with "/" and "*".
>        # Add a final "/." to be sure this only matches a directory:
>        dirpat="`echo $dir | sed 's/\([^.]*\)\./\/\1*/g'`/."

>        # In case $dirpat is empty, set dummy "x" then shift it away:
>        set x $dirpat; shift

>        # Do the cd if we got one match, else print error:
>        if [ "$1" = "$dirpat" ]; then
>           # pattern didn't match (shell didn't expand it)
>           echo "c: no match for $dirpat" 1>&2
>        elif [ $# = 1 ]; then
>           echo "$1"
>           cd "$1"
>        else
>           echo "c: too many matches for $dir:" 1>&2
>           ls -d "$@"
>        fi

>        unset dir dirpat
>     }

The function starts by building a wildcard pattern to match the directory initials. For example, if you type `c u.i.h`., sed makes the pattern `/u*/i*/h*/`. in `$dirpat`. Next, the shell expands the wildcards onto its command-line parameters; the trailing dot makes sure the pattern matches only a directory. If the Bourne shell can't match a wildcard pattern, it leaves the pattern unchanged; the first `if` test spots that. If there was just one match, there will be one command-line parameter left, and the shell cds there. Otherwise, there were too many matches; the function shows them so you can make your pattern longer and more specific.

-- JP

Copyright © 2003 O'Reilly & Associates. All rights reserved.