aliasing cd to pushd – is it a good idea?

Is it a good idea to use the following alias:

cd() {
    pushd $1;

in bash?

I think this would be very useful, since I can then use a series of popds instead of just a cd - once.

Is there any case where this might be a problem?

Asked By: Lazer


Personally, I have these in my bashrc and use them all the time:

  if [ $# -eq 0 ]; then

  builtin pushd "${DIR}" > /dev/null
  echo -n "DIRSTACK: "

  builtin pushd > /dev/null
  echo -n "DIRSTACK: "

  builtin popd > /dev/null
  echo -n "DIRSTACK: "

alias cd='pushd'
alias back='popd'
alias flip='pushd_builtin'

You can then navigate around on the command-line a bit like a browser. cd changes the directory. back goes to the previous directory that you cded from. And flip will move between the current and previous directories without popping them from the directory stack. Overall, it works great.

The only real problem that I’m aware of is the fact that it’s then a set of commands that I’m completely used to but don’t exist on anyone else’s machine. So, if I have to use someone else’s machine, it can be a bit frustrating. If you’re used to just using pushd and popd directly, you don’t have that problem. And while if you just alias cd put not popd, you won’t have the issue of back not existing, you’ll still have the problem that cd doesn’t do quite what you expect on other machines.

I would note, however, that your particular implementation of cd doesn’t quite work like cd in that the normal cd by itself will go to your home directory, but yours doesn’t. The version that I have here doesn’t have that problem. Mine also appends DIRSTACK onto the front of the dirs print out, but that’s more a matter of personal taste more than anything.

So, as I said, I use these aliases all the time and have no problem with them. It’s just that it can be a bit frustrating to have to use another machine and then find them not there (which shouldn’t be surprising, but they’re one of those things that you use so often that you don’t think about them, so having them not work like you’re used to can still be surprising).

Answered By: Jonathan M Davis

This isn’t a direct answer to the question, but I fell in love with the directory history window in 4DOS. So much so that I wrote my own version for Linux (and Cygwin). I’ve never gotten around to making it an easy-to-install utility, but if you know your way around a Bash prompt, it shouldn’t be that hard to get running. Your question inspired me to put it into a Git repo and upload it to GitHub: dirhistory.

Basically, it’s a daemon that collects directory changes from all your shells, and a Cdk program that displays the history and lets you pick any directory to switch to (so you’re not limited to a stack). I find it really useful, and have it bound to Ctrl-PageUp, just like 4DOS did. (I even patched PuTTY so it would send Ctrl-PageUp to Bash.)

Answered By: cjm

For me pushd/popd/dirs is close-to-helpful, but lacking.
So I created a ‘wrapper’ around these called ‘navd’, implemented as essentially a set of 20 aliases. (One of them is a function, really.) The code is below, but here is a brief explanation first.
(One nice thing about “navd” and working on other people’s machines: There is a “no-install” form of running it: As one install option you can simply paste-in the commands that implement “navd” at the bash-prompt, and for the duration of that bash-session for that machine navd will work. That gives zero foot-print in the file-system, yet it is a temporary install. Put those commands in .bashrc for a “real” install, of course.)


navd <path>;   -- will make that path the current dir AND will add it to the stack
                         AS A BONUS: If a relative path is used, this command is added to history
                         with an absolute path instead. This improves navigation even when only
                         using history ... because very often relative-path commands in history
                         are useless if the command changes the current directory. (After all, you
                         would have to be in the directory the command was originally issued
                         from in order for such a command to work correctly.)
navd           -- shows the stack, with index numbers for convenience
navd0          -- makes the first entry on the stack (read left-to-right) **the current dir**
navd1          -- makes the second entry on the stack (read left-to-right) **the current dir**
navd9          -- makes the tenth entry on the stack (read left-to-right) **the current dir**
navd-1         -- makes the first entry on the stack WHEN READ RIGHT-to-LEFT(!) **the current dir**
.                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^
navd-9         -- makes the 9th entry on the stack WHEN READ RIGHT-to-LEFT(!) **the current dir**

Any of the nineteen navd<N> commands rotates the stack so that the directory that becomes current dir also now displays at the front of the stack.
Positive <N> values find a dir counting from the left, with indexing starting at zero.
Negative <N> values find a dir counting from the RIGHT, with indexing starting at -1.
(This follows the convention of how array-indexes can be used in Java and other languages.)

Note: Any “navd” command displays the same stack that “pushd” and “dirs” uses — but displays it WITHOUT the
left-most entry that “dirs” would display (since that entry is not really on the stack — it’s the current directory
and with “dirs” that left-most entry changes if a cd command is entered). (The “cd <path>” command does not affect any behavior of
navd though it certainly does affect the behavior of pushd/dirs/popd. Also … I love to use “cd -” to go “back” once to a directory I just navigated away from, and “cd -”
does not affect the behavior of navd either.)

Bonus: There can be 19 more aliases which do NOT rotate the stack, but merely change dir to the place indicated on the stack.

 nav0 ... nav9   and   nav-1  ... nav-9

2nd Bonus: “navh” shows navd <path> commands from history, for easily loading up the stack with cut-n-paste. (Each is listed only once even if it is in history multiple times, and the list is sorted. Also, entries can be put in a $HOME/.navhignore file to prevent those exact entries from appearing on the navh list.)


Three key behaviors:

  1. If you clear the stack and repeat a specific “navd <path>” command, that path will go on the stack. That’s what I want and expect … but pushd doesn’t do that — it puts the current dir you are navigating AWAY FROM on the stack — so the effect on the stack is variable (feels unpredictable) when you repeat the command.

  2. “navd <path>” will not put the same path on the stack twice.

  3. “navd <path>” puts itself into command history with the absolute path even if the relative path was typed in for the command.

For me, the last three behaviors described make using a “navd <path>” command from history much more helpful than using a “pushd <path>” from history. I really can re-use history to go places. And when I do so I don’t “spoil” my stack.

If you can and want to wrap your brain around it, you can switch between using navd and pushd/dirs/popd. Both use the same stack; just with a different style. For example, use “popd” to remove things from the “navd” stack, or use “dirs -c” to clear the navd stack.

Think of pushd/dirs/popd as “how do I retrace my steps?”.

Think of navd as “how do I hold on to a set of favorite directories, and easily switch between them?”.

Paste the following into a terminal window, and you can immediately start using navd for the duration of that terminal-session.
This is all the code there is to this feature.

# Add 1 function and many related aliases for something like "pushd", called "navd".
# Think of pushd/dirs/popd as "how do I retrace my steps?".
# Think of navd as "how do I hold on to a set of favorite directories, and easily switch between them?".
# Pseudo-code to explain each part of the "navd" bash function just below:
#              If no arguments to the 'navd' command:
#                  If stack has entries, then print the stack one-line-per-dir with each line numbered.
#                  Else, if stack is empty, automatically run the equivalent of the navh command.
#              Else (there **are** arguments to the 'navd' command):
#                  If arg is '--help' or '/?' then show help.
#                  Else    (arg is assumed to be a path to a directory)
#                      Remember the directory we are starting at
#                      Change to dir given as argument (the "arg-dir"), and do a few chores:
#                      Do not use arg-dir literally ... instead, magically put the **absolute** path we arrived at into history.
#                      Set a flag if the arg-dir is already in the stack.
#                      If the flag is set then just show the stack (on one line), else ADD to stack, ROTATE to end-of-stack, and show the stack.
#                      Change to dir we started at and then back to the arg-dir. This allows "cd -" to go back to dir we started at.
#                  End-If
#              End-If
navd () {
    if [[ $1 == '' ]]; then                             #--no arguments to the 'navd' command
        if dirs +1 >/dev/null 2>&1; then                #------stack has entries
            dirs -p | perl -ne 'print (-1+$cn++); print "$_"' | grep -v "^-1";
        else                                            #------stack is empty
            echo "The navd stack is empty. Now running 'navh' in case that's helpful. navd --help works."
            if [[ ! -f $HOME/.navhignore ]]; then echo -n ''>>$HOME/.navhignore;fi;diff --new-line-format="" --unchanged-line-format="" <(history | perl -ne "if (m/^s*d+s+navd ["~./]/) {s/^s*d+s+/  /;s//$//;print}" | sort -u) <(cat $HOME/.navhignore | sort -u);echo "cat $HOME/.navhignore # (Has "`grep -c . <(sort -u $HOME/.navhignore)`" unique lines.)"
    else                                                #--(there **are** arguments to the 'navd' command)
        if [[ $1 == '--help' || $1 == '/?' ]]; then     #------arg is '--help' or '/?'
            echo "The 'navd' functionality is nothing but one bash function and a set of aliases."
            echo "It offers a different style of handy directory navigation than pushd/popd."
            echo "It uses the same 'stack' as pushd. Look in the .bashrc file for details."
            echo "    (Think of pushd/dirs/popd as 'how do I retrace my steps?'."
            echo "     Think of navd as 'how do I remember a set of favorite directories,"
            echo "     and easily switch between them?'.)"
            echo "As of 10/2015, this link has more info:"
            echo "Here is the set of navd-related aliases. None need any parameter:"
            alias | grep 'alias nav' | cut -d= -f1 | grep -v '-' | grep -v 'navh'
            alias | grep 'alias nav' | cut -d= -f1 | grep '-'
            echo "alias navh  # The 'navh' alias has nothing to display until a 'navd <path>' is run. Short for nav-history."
            echo "---- To get started, simpy type navd followed by your favorite path. ----"
            echo "---- navd with no param shows stack. nav0 navigates to first on stack. ----"
        else                                            #------(arg is assumed to be a path to a directory)
            cd "$1" >/dev/null;
            history -s `echo "$PWD" | perl -pe 's/$ENV{'HOME'}/~/;s/ /\\ /g;s/^/navd /'`
            myflag=`dirs -p | perl -pe 's/n/:/' | perl -ne '@a=split(":");$pwd=shift(@a);$flag=0;foreach (@a) {if ($_ eq $pwd) {$flag=1}};print $flag'`
            if [[ $myflag == 1 ]]; then dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"; else pushd .>/dev/null; pushd +1>/dev/null; dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"; fi
            cd "$mypwd"; cd "`dirs -l -0`"
# Aliases for navigating and rotating the "pushd" stack in the style of "navd":
alias navd0='cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"' # "-l" is dash-L, and expands "~" to denote the home dir. Needed inside back-ticks.
alias navd1='cd "`dirs -l +1`";pushd -n +1;cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd2='myd=$PWD;cd "`dirs -l +1`";for i in {1..2};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd3='myd=$PWD;cd "`dirs -l +1`";for i in {1..3};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd4='myd=$PWD;cd "`dirs -l +1`";for i in {1..4};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd5='myd=$PWD;cd "`dirs -l +1`";for i in {1..5};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd6='myd=$PWD;cd "`dirs -l +1`";for i in {1..6};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd7='myd=$PWD;cd "`dirs -l +1`";for i in {1..7};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd8='myd=$PWD;cd "`dirs -l +1`";for i in {1..8};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd9='myd=$PWD;cd "`dirs -l +1`";for i in {1..9};do pushd -n +1>/dev/null;cd "`dirs -l +1`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-1='cd "`dirs -l -0`";pushd -n -0>/dev/null; dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-2='myd=$PWD;cd "`dirs -l -0`";pushd -n -0>/dev/null;cd "`dirs -l -0`";pushd -n -0>/dev/null;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-3='myd=$PWD;cd "`dirs -l -0`";for i in {1..3};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-4='myd=$PWD;cd "`dirs -l -0`";for i in {1..4};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-5='myd=$PWD;cd "`dirs -l -0`";for i in {1..5};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-6='myd=$PWD;cd "`dirs -l -0`";for i in {1..6};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-7='myd=$PWD;cd "`dirs -l -0`";for i in {1..7};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-8='myd=$PWD;cd "`dirs -l -0`";for i in {1..8};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias navd-9='myd=$PWD;cd "`dirs -l -0`";for i in {1..9};do pushd -n -0>/dev/null;cd "`dirs -l -0`";done;cd "$myd";cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
# BONUS commands (beyond the 20). Aliases for navigating but NOT rotating the "navd" stack:
#      Help in remembering: "navd<#>" does more since it both changes the PWD and rotates the stack, whereas "nav<#>" does less
#            (and has one letter less) since "nav<#>" only changes the PWD. Also "navd<#>" acts like the pushd-related command: dirs
#      There is no "nav" command (with no number) so that there will be no conflict if any program called "nav" is used.
alias nav0='cd "`dirs -l +1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav1='cd "`dirs -l +2`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav2='cd "`dirs -l +3`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav3='cd "`dirs -l +4`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav4='cd "`dirs -l +5`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav5='cd "`dirs -l +6`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav6='cd "`dirs -l +7`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav7='cd "`dirs -l +8`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav8='cd "`dirs -l +9`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav9='cd "`dirs -l +10`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-1='cd "`dirs -l -0`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-2='cd "`dirs -l -1`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-3='cd "`dirs -l -2`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-4='cd "`dirs -l -3`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-5='cd "`dirs -l -4`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-6='cd "`dirs -l -5`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-7='cd "`dirs -l -6`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-8='cd "`dirs -l -7`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
alias nav-9='cd "`dirs -l -8`";dirs -p | perl -ne "chomp;s/$/ /;print unless ++$cn==1"'
# BONUS command (beyond the 20). Alias for showing 'history' of all navd commands that add to the stack.
#                Can be used in a new terminal session to quickly add recently used dirs to the navd stack.
alias navh='if [[ ! -f $HOME/.navhignore ]]; then echo -n ''>>$HOME/.navhignore;fi;diff --new-line-format="" --unchanged-line-format="" <(history | perl -ne "if (m/^s*d+s+navd ["~./]/) {s/^s*d+s+/  /;s//$//;print}" | sort -u) <(cat $HOME/.navhignore | sort -u);echo "cat $HOME/.navhignore # (Has "`grep -c . <(sort -u $HOME/.navhignore)`" unique lines.)"'
# Note: When 'navd <relative-path>' is used, then by bash-magic the navd command puts 'navd <absolute-path>' into history,
#       instead. This allows the output of "navh" to be useful regardless of the directory that is current when it is run.
# BONUS commands (beyond the 20). An even shorter alias for navd. An even shorter alias for navh.
alias nd='navd'
alias nh='if [[ ! -f $HOME/.navhignore ]]; then echo -n "">>$HOME/.navhignore;fi;diff --new-line-format="" --unchanged-line-format="" <(history | perl -ne "if (m/^s*d+s+navd ["~./]/) {s/^s*d+s+/  /;s//$//;print}" | sort -u) <(cat $HOME/.navhignore | sort -u);echo "cat $HOME/.navhignore # (Has "`grep -c . <(sort -u $HOME/.navhignore)`" unique lines.)"'

These aliases are based on “bash” commands. Special care is taken to preserve the normal behavior of “cd -“. (A lot of the time I use “cd -” instead of bothering with either pushd or navd — because “cd -” is very handy for going back to the last “place” you were, or switching between only 2 places, and it works everywhere with no install.)

These commands can, of course, be put into the .bashrc file for a more permanent installation of them.

Answered By: Dana Forsberg

Here’s yet another solution you might like. I wrote this after playing with the solution by @cjm. It uses the dialog command to create an ncurses type menu from the output of dirs. Selecting an item will bring that directory to the top of the stack and cd into it. This has the benefit over dirhistory of giving each terminal emulator its own buffer of directory history, and being a bit easier to install.

To install:
Once you’ve aliased cd to pushd, install dialog, then just put this funtion in your bashrc:

    dirIter=$(dialog --backtitle 'dirmenu' --clear --cancel-label "Exit" --menu "Please select:" 0 0 0 $(dirs) 3>&2 2>&1 1>&3)
    cmd="builtin cd ~$dirIter"
    eval $cmd

I like this a bit better than running dirs -v, then running another command to pop or cd to the directory I want. Dialog menus can also be highly customized through its dialogrc.

So to answer your question, yes, I think aliasing pushd to cd is a great idea. You are unlikely to have buffer overflow issues if you are rebooting your machine regularly at least for updates. Though I’d be careful using cd when scripting; cd in a while loop could potentially cause buffer overflow issues. I’m not sure what controls the dirs/pushd buffer size.

Answered By: Overdr0ne
Categories: Answers Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.