How to make "cd ..." goup two directories?

1

So, currently on macOS I have aliases set up like this...

alias ...='cd ../..'
alias ....='cd ../../..'

This allows me to go up multiple directories by typing in ... in my terminal. However, in my previous setup, I had it so typing in cd ... would go up multiple directories. I'm trained to write cd in the beginning... how do I change my aliases for it to work the way I desire?

Jimmy Huch

Posted 2018-11-25T08:26:42.000

Reputation: 113

Answers

2

With the help of https://stackoverflow.com/questions/941338/how-to-pass-command-line-arguments-to-a-shell-alias your problem can be solved like this:

alias cd='function _cd(){ cd ${1/.../..\/..}; }; _cd '

The parameter expansion in the function replaces all occurrences of '...' by '../..', thus it is not a really clean solution, but I think it may still be acceptable or can be improved.

Hartmut Braun

Posted 2018-11-25T08:26:42.000

Reputation: 186

This actually works quite nicely. I'm not sure how or why, but this works for cd .., cd ..., cd ...., and cd ..... (I haven't tried for more than 5 dots, but this is all I need :-D ) – Jimmy Huch – 2019-01-23T16:27:01.697

I'm confused. On my system (MacOs Mojave, bash version 3.2.57), only cd ... works. With 4 dots I get, as expected, the error message -bash: cd: ../...: No such file or directory – Hartmut Braun – 2019-01-23T17:35:50.543

0

You cannot have an alias name with spaces. To do what you want with aliases you need to take advantage of this property:

If the value of the alias replacing the word ends in a <blank>, the shell shall check the next command word for alias substitution; this process shall continue until a word is found that is not a valid alias or an alias value does not end in a <blank>.

(source), so cd and consecutive ... are resolved as aliases.

You need aliases like this:

cd='cd '
...='../..'
....='../../..'
# and so on

But then if you happen to have a directory named ls and you type cd ls, then ls will also be prone to alias substitution. If ls is an alias, this will backfire.

Pure alias approach is therefore wrong. This function will do (use it without the above aliases):

cd() {
       if [ "$#" -eq 0 ]; then
          command cd
       elif [ "$(printf '%s' "$1" | wc -l)" -eq 0 ]; then
          command cd "$(printf '%s' "$1" | sed '/^\.\.\+$/ {s|..|.|; s|.|../|g }')";
       else
          command cd "$1"
       fi
     }

(Note: to make it work in zsh, invoke set -o POSIX_BUILTINS or replace every command with builtin in the function body).

sed gets the argument passed to the function and changes it iff it consists of two or more dots; the tool removes the first dot and replaces every dot (that's left) with ../.

There is additional logic:

  • [ "$#" -eq 0 ] allows the sole cd (without arguments) to work as it should.
  • [ "$(printf '%s' "$1" | wc -l)" -eq 0 ] is to prevent sed to alter multi-line directory names (e.g. foo<newline>..... which is a valid name). Note that for usual names (not containing newlines) wc -l returns 0 (not 1, POSIX doesn't consider a "line" without terminating newline to be a line), hence we test against 0.

And there's a quirk:

  • In s|..|.| the first two dots are not literal dots, this is regex. One may say it should be s|\.\.|.| but notice /^\.\.\+$/ ensures there are only dots in the line in the first place. The same applies to the first dot in s|.|../|g.

Kamil Maciorowski

Posted 2018-11-25T08:26:42.000

Reputation: 38 429

Why do you do printf '%s' "$1" | instead of <<< "$1"?  Just for POSIX? – Scott – 2018-11-25T14:41:23.293

@Scott Yes, for POSIX. My first (not published) draft used <<<. – Kamil Maciorowski – 2018-11-25T14:43:44.333

The function didn't work for me... normal cd just stopped working :/ It might be because I'm using zsh – Jimmy Huch – 2019-01-23T16:23:09.550

@JimmyHuch Thank you for the feedback. My fix for zsh is now just under the function code. – Kamil Maciorowski – 2019-01-23T16:46:58.847