10

How do I change to a near-identical path with a different low-level parent? If you’re working in for instance ~/foobar/foo/data/images/2020/01/14/0001/ and need to get to the same path in bar instead of foo, how can you get there without typing out cd ~/foobar/bar/data/images/2020/01/14/0001/? Surely there’s some elegant and/or kludgy solution.

4
  • 3
    Related: askubuntu.com/q/3759/1156790
    – thanasisp
    Commented Dec 19, 2020 at 10:53
  • 2
    By the way, you have included the perfect example, where the first occurence of the pattern should not be replaced...
    – thanasisp
    Commented Dec 19, 2020 at 10:55
  • Heh, yeah, I kind of had that in mind when writing that example. And @thanasisp more than related, it’s pretty identical, wasn’t sure how to search it though. But there seems to be a more complete answer here than there.
    – Frungi
    Commented Dec 20, 2020 at 1:22
  • Using set -o vi you can use vi key bindings. When the cursor is at the end, you could do <ESC>F/;;;;lcwbar (i.e. search for "/" backwards, repeat the search several times (";") until the cursor is on the "/" before "foo", move one character to the right (l) and then change word to "bar". I find this approach quite flexible.
    – Rolf
    Commented Dec 22, 2020 at 7:46

5 Answers 5

16

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/.

1
  • 1
    ${var/pattern/replacement} is also from the Korn shell but came much later (ksh93). See also $var:s/foo/bar/ in (t)csh or zsh (already in the first release of csh in 2BSD in the late 70s) Commented Dec 19, 2020 at 8:16
2

It's easy.

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

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

1
2

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.

0

When I've needed to work between two directories quickly and more than a couple of times the following example shows what works for me:

$ pwd
/home/chris/tmp
$ ln -s /home/mythtv/video/tv/tmp 
$ cd tmp
$ ls
$ cd ../ 
$ mv 852f.mkv tmp
$ ls tmp
852f.mkv
$ rm -i tmp
rm: remove symbolic link 'tmp'? y
$ ls /home/mythtv/video/tv/tmp
852f.mkv
0

Sometimes it's easier to use the mouse, assuming you're using a good terminal emulator on a graphical desktop. Or in screen or a modern equivalent, you can use keyboard controls to copy/paste some text.

$ pwd
/some/long/path

copy/paste that path onto a cd command (i.e. double-click on it to select the whole thing, or triple to get the whole line if there are spaces. If so then type a single-quote before pasting it).

Then use your shell's line-editing keys to edit an early component of it, e.g. in Bash's emacs mode,

  • ctrl-a to move the cursor to the start of the line
  • ctrl-right-arrow (or alt-f) a couple times to go forward by words until the right component.
  • alt-d or alt-backspace to delete a whole word forward or backward.
  • type a replacement (with tab completion) and hit return

If you fumble something, it's only a cd command so it won't do anything destructive.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .