views:

146

answers:

3

The Korn shell used to have a very useful option to cd for traversing similar directory structures e.g. given the following directorys

  • /home/sweet/dev/projects/trunk/projecta/app/models
  • /home/andy/dev/projects/trunk/projecta/app/models

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 in bash.

EDIT 1

Following on from Michal's excellent answer I have now created the following bash function called scd (For Sideways cd)

function scd {
  cd "${PWD/$1/$2}"
}

EDIT 2

Thanks to @digitalross I can now reproduce the ksh functionality exactly with the code from below (With the addition of a pwd to tell you where you have changed to)

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

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
}
DigitalRoss
That's event better. Thank you. I wasn't aware you could do that. I really must get up to date with these new-fangled shells. I don't use very much more than was available in /bin/sh when I learnt it in 1987. Looks like shells have come a long way since then ;-)
Steve Weet
Heh, well, I did use the bashism "builtin" in the script. The older way of writing that would have been to define "cd" and use "chdir" to do the real work.I don't know that I would start with the huge bash man page. I think ash and dash are generally better to script for, partly because the docs are easier to read, partly because you then get portable scripts. You might want to look up Ash_shell and Debian_Almquist_shell on Wikipedia, and maybe check this out:http://netbsd.gw.com/cgi-bin/man-cgi?sh++NetBSD-current
DigitalRoss
But man, this is bash. Even POSIX shell doesn't require such an ugly tests; plain [[ "$2" ]] is much better.
Michał Górny
+2  A: 
cd "${PWD/sweet/andy}"
Michał Górny
Nice idea. Thank you
Steve Weet
Sorry to accept and then take it away again but I just have to accept Dennis's solution
Steve Weet
+2  A: 

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.

Dennis Williamson
+1 Thanks Dennis, a nicely thought out and debugged answer
Steve Weet