2
  • To move from ~/aaa/foo/bbb to ~/aaa/bar/bbb, one can use cd foo bar
  • and from ~/foobar/foo/www to ~/foobar/bar/www: cd "/foo/" "/bar/"

But how is it possbile, using the same technique,

  • to move from ~/aaa/foo/foo/bbb to ~/aaa/bar/bar/bbb
  • or from ~/foobar/foo/foo/www to ~/foobar/bar/bar/www?

2 Answers 2

3
cd foo bar

Is a feature copied from the Korn shell that chdir()s to a path that is computed from the current working directory ($PWD) with the first occurrence of foo replaced with bar.

That's all there is. If you want any other type of transformation, you need to do it by hand.

For instance, to cd to $PWD with all occurrences of foo replaced with bar, either use the csh-style¹:

cd $PWD:gs/foo/bar

Or the Korn-style²:

cd ${PWD//foo/bar}

(note that contrary to cd foo bar, those don't print the path of the new working directory).

For your ~/foobar/foo/foo/www to ~/foobar/bar/bar/www, assuming $HOME doesn't contain $HOME doesn't contain /foo:

cd $PWD:gs:foo/:bar/

See also:

cd $PWD:fs:/foo/:/bar/

There's a subtle difference between the f and g repeating modifiers:

  • g acts like the g flag of sed's s command: it replaces all occurrences, looking for all those occurrences one after the other in one pass in the string
  • while fs:string:replacement repeats the s:string:replacement from the start as long as it does any modification. Note that while g is limited to working with :s/string/replacement, f can be used with any modifier; for instance $file:fr is like $file:r (get the root name, IOW remove the extension) except it removes all extensions.

gs:/foo/:/bar/ like sed s:/foo/:/var/:g would change ~/foobar/foo/foo/www into ~/foobar/bar/foo/www only, because after the first /foo/ has been replaced with /bar/, s carries on searching for other occurrences in what's left but that's foo/www which doesn't contain any /foo/.

While fs:/foo/:/bar/ is more like sed -e :1 -e s/foo/bar/ -e t1³ and works because s is called first on /home/you/foobar/foo/foo/www and then on /home/you/foobar/bar/foo/www.

Also note that in $var:s/foo/bar, foo is taken as a fixed string (unless the histsubstpattern option is set) while in ${var/foo/bar}, it's taken as a pattern. The next version of zsh will have $var:S/foo/bar where foo is taken as a pattern regardless of the setting of the histsubstpattern option.

Another approach to replace all path components that are foo as a whole (including the last that the above approach doesn't address) would be to

  1. split on /
  2. replace all matching elements in the resulting array.
  3. join back with /
cd ${(j[/])${(s[/])PWD}:/foo/bar}

Here using zsh's ${var:/pattern/replacement} extension over ksh's ${var/pattern/replacement} for the pattern to match the whole string only.

Or, with histsubstpattern (or :S in the next version), use the W[/] modifier for the s modifier to act on each /-separated Word:

set -o histsubstpattern # typically in ~/.zshrc
cd $PWD:W[/]s/#%foo/bar

(where # anchors the pattern at the start, and % at the end4).


¹ Well, technically, the :s/string/replacement is from csh, but g is a zsh extension, similar to tcsh's a. $PWD is from ksh, though tcsh has $cwd as an equivalent. Of course, in (t)csh, as usual, you'd also need a :q to "quote" the result.

² Well, in ksh, as usual, you'd also need to quote the expansion to prevent split+glob

³ that's not strictly equivalent as sed's t1 would repeat (branch to the :1 label) if any substitution was done while zsh's :fs/pattern/replacement/ repeats if a substitution was done and resulted in a change. For instance with s/foo/foo/, sed would run in an infinite loop, while zsh's f would stop at once.

4 like in ksh's ${var/pattern/replacement} except that ksh can't combine the two.

2

The two-argument form of cd only performs a single substitution. You'll have to use a different technique.

You can use history modifiers on $PWD to perform substitutions that can be chained.

~/aaa/foo/foo/bbb% $PWD:gs/foo/bar            # to ~/aaa/bar/bar/bbb
~/foobar/foo/foo/www% $PWD:gs:foo/:bar/       # to ~/foobar/bar/bar/www
~/aaa/foo/foo/bbb% $PWD:s/foo/bar/:s/foo/qux  # to ~/aaa/bar/qux/bbb

Doing complex substitutions gets hard to follow, though. What I usually do in such cases is to edit PWD: type $PWD then press Tab to replace the variable expansion by its value, then edit the places I want to modify. Depending on your completion settings and key bindings, you may need to press a different key to expand the variable's value rather than do completion while preserving the expansion.

1
  • Thanks a lot for the PWD technique, Gilles!
    – jsx97
    Commented Aug 8 at 22:05

You must log in to answer this question.

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