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.