Steve Weet
Steve Weet

Reputation: 28402

Is there a Bash shortcut for traversing similar directory structures?

The KornShell (ksh) used to have a very useful option to cd for traversing similar directory structures; e.g., given the following directories:

Then if you were in the /home/sweet... directory then you could change to the equivalent directory in andy's structure by typing

cd sweet andy

So if ksh saw 2 arguments then it would scan the current directory path for the first value, replace it with the second and cd there. Is anyone aware of similar functionality built into Bash? Or if not, a hack to make Bash work in the same way?

Upvotes: 5

Views: 858

Answers (3)

Dennis Williamson
Dennis Williamson

Reputation: 360153

Other solutions offered so far suffer from one or more of the following problems:

  • Archaic forms of tests - as pointed out by Michał Górny
  • Incomplete protection from directory names containing white space
  • Failure to handle directory structures which have the same name used more than once or with substrings that match: /canis/lupus/lupus/ or /nicknames/Robert/Rob/

This version handles all the issues listed above.

cd () 
{ 
    local pwd="${PWD}/"; # we need a slash at the end so we can check for it, too
    if [[ "$1" == "-e" ]]
    then
        shift
        # start from the end
        [[ "$2" ]] && builtin cd "${pwd%/$1/*}/${2:-$1}/${pwd##*/$1/}" || builtin cd "$@"
    else
        # start from the beginning
        [[ "$2" ]] &&  builtin cd "${pwd/\/$1\///$2/}" || builtin cd "$@"
    fi
}

Issuing any of the other versions, which I'll call cdX, from a directory such as this one:

    /canis/lupus/lupus/specimen $ cdX lupus familiaris
    bash: cd: /canis/familiaris/lupus/specimen: No such file or directory

fails if the second instance of "lupus" is the one intended. In order to accommodate this, you can use the "-e" option to start from the end of the directory structure.

    /canis/lupus/lupus/specimen $ cd -e lupus familiaris
    /canis/lupus/familiaris/specimen $

Or issuing one of them from this one:

    /nicknames/Robert/Rob $ cdX Rob Bob
    bash: cd: /nicknames/Bobert/Rob: No such file or directory

would substitute part of a string unintentionally. My function handles this by including the slashes in the match.

    /nicknames/Robert/Rob $ cd Rob Bob
    /nicknames/Robert/Bob $

You can also designate a directory unambiguously like this:

    /fish/fish/fins $ cd fish/fins robot/fins
    /fish/robot/fins $

By the way, I used the control operators && and || in my function instead of if...then...else...fi just for the sake of variety.

Upvotes: 5

DigitalRoss
DigitalRoss

Reputation: 146093

No, but...

Michał Górny's substitution expression works nicely. To redefine the built-in cd command, do this:

cd () {
  if [ "x$2" != x ]; then
    builtin cd ${PWD/$1/$2}
  else
    builtin cd "$@"
  fi
}

Upvotes: 2

Michał Górny
Michał Górny

Reputation: 19253

cd "${PWD/sweet/andy}"

Upvotes: 3

Related Questions