Changing directory by changing one early word in a pathname

In some shells, e.g. ksh and zsh, doing cd word1 word2 would change to a directory given by changing the first occurrence of word1 in the pathname of the current directory to word2.

For example, in the zsh shell:

$ pwd
/usr/local/sbin
$ cd sbin bin
/usr/local/bin
$

In other shells that support the non-standard ${variable/pattern/replacement} parameter substitution originally found in ksh93, you may use ${PWD/word1/word2} to create the pathname of the directory to change into:

$ pwd
/usr/local/sbin
$ cd "${PWD/sbin/bin}"
$ pwd
/usr/local/bin

In those shells (bash, for example), you could even create your own naive cd function to handle two arguments in the way that ksh and zsh does it, like so:

cd () {
    if [ "$#" -eq 2 ] && [[ $1 != -* ]]; then
        command cd -- "${PWD/$1/$2}" &&
        printf 'New wd: %s\n' "$PWD"
    else
        command cd "$@"
    fi
}

The [ "$#" -eq 2 ] detects when the special cd behavior should be triggered (when there are exactly two command line arguments), but we test with [[ $1 != -* ]] to not trigger the special behavior if you use an option with cd. Using command cd instead of cd inside the function avoids calling the function recursively.

Testing that in bash:

$ cd /usr/local/sbin
$ cd sbin bin
New wd: /usr/local/bin
$ cd local ''
New wd: /usr/bin
$ cd bin sbin
New wd: /usr/sbin
$ cd sbin local/sbin
New wd: /usr/local/sbin
$ cd '*/' /
New wd: /sbin

Notice that the last command replaces using a pattern matching up to and including the last /; the pattern must be quoted. To disallow patterns and to always treat the first argument as a word, use command cd "${PWD/"$1"/$2}" in the function (notice the quoting of $1).

To additionally force the replacement to only affect a complete directory name, use command cd "${PWD/"/$1/"/"/$2/"}". Artificially inserting / before and after both arguments would avoid matching substrings of directory names, but would make it incompatible with the way this works in zsh and ksh and it would no longer allow you to make substitutions in the last part of the directory path as there is no / at the end (you can only provide a certain level of hand-holding before the extra "help" starts to be a hindrance).

This would make cd foo bar work with the example that is in the question, though. You would otherwise have to make sure not to match foo in foobar in some other way, for example with cd foo/ bar/.


It's easy.

cd "$( echo "$PWD" | sed -e 's%/foo/%/bar/%' )"

sed uses the character following the s command as its delimiter.


On the cli, you can use a modifier like this example:

$ mkdir -p foobar/foo/data/images/2020/01/14/0001/
mkdir: created directory 'foobar'
mkdir: created directory 'foobar/foo'
mkdir: created directory 'foobar/foo/data'
mkdir: created directory 'foobar/foo/data/images'
mkdir: created directory 'foobar/foo/data/images/2020'
mkdir: created directory 'foobar/foo/data/images/2020/01'
mkdir: created directory 'foobar/foo/data/images/2020/01/14'
mkdir: created directory 'foobar/foo/data/images/2020/01/14/0001/'
$ ^bar/foo^bar/bar^
mkdir -p foobar/bar/data/images/2020/01/14/0001/
mkdir: created directory 'foobar/bar'
mkdir: created directory 'foobar/bar/data'
mkdir: created directory 'foobar/bar/data/images'
mkdir: created directory 'foobar/bar/data/images/2020'
mkdir: created directory 'foobar/bar/data/images/2020/01'
mkdir: created directory 'foobar/bar/data/images/2020/01/14'
mkdir: created directory 'foobar/bar/data/images/2020/01/14/0001/'

Explanation:

The first command creates nested directories starting with foobar. The second command uses the modifier ^ to replace a string in the previous command with a new string. It is easy to make a mistake by typing the following:

^foo^bar^

However, this will change the command to

mkdir -p barbar/foo/data/images/2020/01/14/0001/

because it will also affect the leading foo. To avoid this, you can use some characters before or after the intended string.

I used mkdir in this example, but the cd is similar. HTH.