If switching to zsh is an option, you could use global aliases there for that:
alias -g ...=../..
But in any case that's only expanded when ... is recognised as a full delimited token on its own. It will be expanded in echo ... or echo ...> file or (echo ...) but not in echo .../x or echo ~/... or echo "...").
Normal csh-style aliases, whether it's in bash or zsh are only expanded when in command position or after another alias expansion ending in a blank character. In zsh, though, if you set the autocd option, entering ../.. or another directory as the command name gets turned into cd ../.., so
$ set -o autocd
$ alias ...=../.. ....=../../..
$ ...
Would be a way to cd into ../.., but then again so would alias ...='cd ../..' or ...() cd ../...
A better approach that I've been using for many years is to bind the . key to something that inserts /.. instead of . if what's left of the cursor ends in ..:
magic-dot() {
if [[ $LBUFFER = (|*[[:blank:]/]).. ]]; then
repeat ${NUMERIC-1} LBUFFER+=/..
else
zle self-insert
fi
}
zle -N magic-dot
bindkey . magic-dot
Then entering ... gets immediately and visually transformed to ../.. and pressing . again changes it to ../../.. and so on. If in emacs mode, .. followed by Alt+4,. makes it ../../../../.. for instance (/.. appended 4 times) using the usual NUMERIC prefix handling.
It used to sometimes get in the way when copy/pasting things that contain ..., but that's no longer a problem now that bracketed paste support has been introduced. To enter a literal ..., you can press Ctrl+v before the third . or use backslashes, quoting or anything that makes so what's left of the cursor ($LBUFFER above) doesn't end in <blanks-or-slash>.. such as .\.., ..\., whatever/'...'...
An intentional limitation of the above is that for instance in sort -o..., the ... is not expanded. Use sort -o ... instead. Same goes for cd '.../***' -> cd ...'/***'. You can change the test above to [[ $LBUFFER = (|*[^.]).. ]] for the ... to be expanded to ../.. in more contexts although that would increase the probability of it being expanded in places you wouldn't like it to.
We can make the numeric argument handling a bit more useful by allowing cd Alt+4. to cd 4 levels up (expand to ../../../..) for instance with:
magic-dot() {
if (( NUMERIC )) && [[ $LBUFFER = (|*[[:blank:]/:]) ]]; then
LBUFFER+=..
(( NUMERIC-- ))
fi
if [[ $LBUFFER = (|*[[:blank:]/]).. ]]; then
repeat ${NUMERIC-1} LBUFFER+=/..
else
zle self-insert
fi
}
zle -N magic-dot
bindkey . magic-dot
As mentioned by @Gilles in comment, recent versions of bash have added limited support for editing the line buffer in commands bound to keys, so you could do something similar there with:
insert_before_cursor() {
# for the equivalent of zsh's repeat $1 LBUFFER+=$2
local i
for (( i = 0; i < $1; i++ )); do
READLINE_LINE=${READLINE_LINE:0:READLINE_POINT}$2${READLINE_LINE:READLINE_POINT}
(( READLINE_POINT += ${#2} ))
done
}
magic-dot() {
(( ${READLINE_ARGUMENT-1} > 0 )) || return
if [[ -v READLINE_ARGUMENT && ${READLINE_LINE:0:READLINE_POINT} = ?(*[[:blank:]/]) ]]; then
insert_before_cursor 1 ..
(( READLINE_ARGUMENT-- ))
fi
if [[ ${READLINE_LINE:0:READLINE_POINT} = ?(*[[:blank:]/]).. ]]; then
insert_before_cursor "${READLINE_ARGUMENT-1}" /..
else
insert_before_cursor "${READLINE_ARGUMENT-1}" .
fi
}
bind -x '".":magic-dot'
# work around a bug in current versions of bash for the numeric
# argument to work properly, that means however that you lose the
# insert-last-argument normally bound to Meta-. (also on Meta-_)
bind -x '"\e.":magic-dot'
You need bash 4.0 or above for $READLINE_LINE (not $READLINE_LINE_BUFFER as misspelled in some of the documentation), and $READLINE_POINT, and 5.2 or above for $READLINE_ARGUMENT, though as noted above it currently doesn't work properly yet.