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?


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.

  • 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 Jan 23 at 16:27
  • 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 Jan 23 at 17:35

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 }')";
          command cd "$1"

(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.
  • Why do you do printf '%s' "$1" | instead of <<< "$1"?  Just for POSIX? – Scott Nov 25 '18 at 14:41
  • @Scott Yes, for POSIX. My first (not published) draft used <<<. – Kamil Maciorowski Nov 25 '18 at 14:43
  • The function didn't work for me... normal cd just stopped working :/ It might be because I'm using zsh – Jimmy Huch Jan 23 at 16:23
  • @JimmyHuch Thank you for the feedback. My fix for zsh is now just under the function code. – Kamil Maciorowski Jan 23 at 16:46

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.