48

What is a fast command line way to switch between multiple directories for system administration? I mean, I can use pushd . and popd to toggle, but what if I want to store multiples and cycle through them, rather than permanently popping them off the bottom of the stack?

5
  • $CDPATH perhaps? May 30, 2016 at 19:13
  • 6
    oh my - i just learned about cd - after googling instigated from this question.
    – Nacht
    May 31, 2016 at 6:24
  • @Nacht: How did you navigate the file structure before??
    – loneboat
    May 31, 2016 at 18:42
  • 1
    Did cd everywhere by hand. Was painful. Used auto completion with tab key, though.
    – Volomike
    May 31, 2016 at 18:46
  • 1
    Maybe you want to give z a try. Extremely handy when you jump around a lot within the same couple of dirs.
    – Rolf
    Jul 4, 2016 at 13:41

13 Answers 13

43

bash's builtin pushd with the + and - options can rotate the directory stack. The syntax can be a little confusing, perhaps because that stack is a zero-based array. These simple wrapper functions cycle through the directory stack:

# cd to next     directory in stack (left  rotate)
ncd(){ pushd +1 > /dev/null ; }
# cd to previous directory in stack (right rotate)
pcd(){ pushd -0 > /dev/null ; }

Test: set up a stack of four directories.

dirs -c   # clear directory stack
cd /home ; pushd /etc ; pushd /bin ; pushd /tmp

Now /tmp is the current directory, and the stack looks like:

/tmp /bin /etc /home

Change to next directory in stack, (and show it), four times:

ncd ; pwd ; ncd ; pwd ; ncd ; pwd ; ncd ; pwd

Output:

/bin
/etc
/home
/tmp

Change to previous directory in stack, (and show it), four times:

pcd ; pwd ; pcd ; pwd ; pcd ; pwd ; pcd ; pwd

Output:

/home
/etc
/bin
/tmp

A note on cd -: Wildcard's answer helped illustrate how cd - doesn't use the $DIRSTACK array, (it uses the $OLDPW variable), so that cd - doesn't effect $DIRSTACK the way a stack-based swap should. To correct that, here's a simple $DIRSTACK-based swap function:

scd() { { pushd ${DIRSTACK[1]} ; popd -n +2 ; } > /dev/null ; }

Test:

dirs -c; cd /tmp; \
pushd /bin; \
pushd /etc; \
pushd /lib; \
pushd /home; \
scd; dirs; scd; dirs

Output:

/bin /tmp
/etc /bin /tmp
/lib /etc /bin /tmp
/home /lib /etc /bin /tmp
/lib /home /etc /bin /tmp
/home /lib /etc /bin /tmp
3
  • My frustration is that I want to put items in the stack and keep them there until I choose to clear the stack. That way, I can push, push, push, then cycle through each of three dirs as necessary. Say for instance I need to flip between three directories as if they are like browser tabs, and go back and forth, until I close a tab. Does this address this in a command line way?
    – Volomike
    May 30, 2016 at 3:45
  • 5
    Yes, I believe it does, try it out. To remove the top item, do popd, to remove it without changing dirs, do popd -n. To clear the stack do dirs -c. Too bad the bash designers buried such useful functions in non-intuitive syntax. See man bash under pushd, dirs, etc.
    – agc
    May 30, 2016 at 3:56
  • Works! And was clear as mud in the docs. I like the simplicity of it, now that I know the technique.
    – Volomike
    May 30, 2016 at 4:05
31

Use pushd and then the special names for the directories in your directory stack: ~1, ~2, etc.

Example:

tmp $ dirs -v
 0  /tmp
 1  /tmp/scripts
 2  /tmp/photos
 3  /tmp/music
 4  /tmp/pictures
tmp $ cd ~3
music $ dirs -v
 0  /tmp/music
 1  /tmp/scripts
 2  /tmp/photos
 3  /tmp/music
 4  /tmp/pictures
music $ cd ~2
photos $ cd ~4
pictures $ cd ~3
music $ cd ~1
scripts $ 

The most effective way to use pushd in this way is to load up your directory list, then add one more directory to be your current directory, and then you can jump between the static numbers without affecting the position of the directories in your stack.


It's also worth noting that cd - will take you to the last directory you were in. So will cd ~-.

The advantage of ~- over just - is that - is specific to cd, whereas ~- is expanded by your shell the same way that ~1, ~2, etc. are. This comes in handy when copying a file between very long directory paths; e.g.:

cd /very/long/path/to/some/directory/
cd /another/long/path/to/where/the/source/file/is/
cp myfile ~-

The above is equivalent to:

cp /another/long/path/to/where/the/source/file/is/myfile /very/long/path/to/some/directory/
7
  • 1
    Can also use cd -
    – Volomike
    May 31, 2016 at 18:48
  • cd - acts like a Forth DROP DUP, e.g. if one starts with in directory a, with stack a b c d, a cd - changes the stack to b b c d. Perhaps that's a bash bug however, since a second cd - changes the stack back to a b c d. From this we can infer that cd - uses some separate data structure to store the previous directory, in addition to changing the stack.
    – agc
    May 31, 2016 at 21:42
  • 3
    @agc, cd - uses $OLDPWD and nothing else.
    – Wildcard
    May 31, 2016 at 22:14
  • So maybe cd - was the earlier bash feature, then later they added a stack, but feared breaking the old code more than they wanted a simpler design. Correction to previous, cd - acts something like a Forth OLDPWD @ SWAP OLDPWD ! DUP . Unlike cd foo, the cd - prints the name of the swapped-in directory.
    – agc
    May 31, 2016 at 22:46
  • Or there's always the interactive pushd wrapper (other answers to that question include autojump etc. etc.).
    – Useless
    Jun 1, 2016 at 14:08
13

I suggest you install fasd. It gives you the ability to quickly jump to any directory you've already been in by typing only a small fraction of its name.

Example: if you've visited /home/someName/scripts/, you could jump there just by typing z scr for example. It's way more convenient that remembering the ordering in the history stack or anything similar.

11

When you cd somewhere, Bash stores the old working directory an environment variable, $OLDPWD.

You can switch back to that directory with cd -, which is equivalent to cd "$OLDPWD".

You can bounce back and forth between directories like so:

blue$ cd ~/green
green$ cd -
blue$ cd -
green$
6

I wrote a script named xyzzy to do this:

#!/bin/bash

i="$1"
i=$((${i//[^0-9]/}))
i="$(($i-1+0))"

b="$2"
b=$((${b//[^0-9]/}))
b="$(($b-1+0))"

if [ -z "$XYZZY_INDEX" ]; then
    XYZZY_INDEX="$((-1))"
fi

if [ ! -f "/tmp/xyzzy.list" ]; then
    touch /tmp/xyzzy.list
    chmod a+rw /tmp/xyzzy.list
fi
readarray -t MYLIST < /tmp/xyzzy.list

showHelp(){
read -r -d '' MYHELP <<'EOB'
xyzzy 1.0

A command for manipulating escape routes from grues. Otherwise known as a useful system admin
tool for storing current directories and cycling through them rapidly. You'll wonder why this
wasn't created many moons ago.

Usage: xyzzy [options]

help/-h/--help      Show the help.

this/-t/--this      Store the current directory in /tmp/xyzzy.list

begone/-b/--begone  Clear the /tmp/xyzzy.list file. However, succeed with a number and
            it clears just that item from the stored list.

show/-s/--show      Show the list of stored directories from /tmp/xyzzy.list

. #         Use a number to 'cd' to that directory item in the stored list. This syntax is odd:

            . xyzzy 2

            ...would change to the second directory in the list

. [no options]      Use the command alone and it cd cycles through the next item in the stored 
            list, repeating to the top when it gets to the bottom. The dot and space before xyzzy
            is required in order for the command to run in the current shell and not a subshell:

            . xyzzy

Note that you can avoid the odd dot syntax by adding this to your ~/.bashrc file:

  alias xyzzy=". xyzzy"

and then you can do "xyzzy" to cycle through directories, or "xyzzy {number}" to go to a
specific one.

May you never encounter another grue.

Copyright (c) 2016, Mike McKee <https://github.com/volomike>
EOB
    echo -e "$MYHELP\n"
}

storeThis(){
    echo -e "With a stroke of your wand, you magically created the new escape route: $PWD"
    echo "$PWD" >> /tmp/xyzzy.list
    chmod a+rw /tmp/xyzzy.list
}

begoneList(){
    if [[ "$b" == "-1" ]]; then
        echo "POOF! Your escape routes are gone. We bless your soul from the ever-present grues!"
        >/tmp/xyzzy.list
        chmod a+rw /tmp/xyzzy.list
    else
        echo -n "Waving your wand in the dark, you successfully manage to remove one of your escape routes: "
        echo "${MYLIST[${b}]}"
        >/tmp/xyzzy.list
        chmod a+rw /tmp/xyzzy.list
        for x in "${MYLIST[@]}"; do
            if [[ ! "$x" == "${MYLIST[${b}]}" ]]; then
                echo "$x" >> /tmp/xyzzy.list
            fi
        done
    fi
}

showList(){
    echo -e "These are your escape routes:\n"
    cat /tmp/xyzzy.list
}

cycleNext(){
    MAXLINES=${#MYLIST[@]}
    XYZZY_INDEX=$((XYZZY_INDEX+1))
    if [[ $XYZZY_INDEX > $(($MAXLINES - 1)) ]]; then
        XYZZY_INDEX=0
    fi
    MYLINE="${MYLIST[${XYZZY_INDEX}]}"
    cd "$MYLINE";
}

switchDir(){
    MYLINE="${MYLIST[${i}]}"
    cd "$MYLINE";
}

if [[ "$@" == "" ]];
then
    cycleNext
fi;

while [[ "$@" > 0 ]]; do case $1 in
    help) showHelp;;
    --help) showHelp;;
    -h) showHelp;;
    show) showList;;
    -s) showList;;
    --show) showList;;
    list) showList;;
    this) storeThis;;
    --this) storeThis;;
    -t) storeThis;;
    begone) begoneList;;
    --begone) begoneList;;
    *) switchDir;;
    esac; shift
done

export XYZZY_INDEX

The way I use this is to copy into /usr/bin folder and then chmod a+x on it. Then, I edit my root and user account ~/.bashrc file to include these lines at the bottom:

alias xyzzy='. xyzzy'
alias xy='. xyzzy'

The 'xy' is a shortened form of the command for faster typing.

Then, I can store the current directory in the list with...

xyzzy this

...and repeat as necessary. Once I fill this list with the directories I need, they remain there until I reboot the computer because that's when /tmp is cleared out again. I can then type...

xyzzy show

...to list the currently saved directories. In order to switch to a directory, I have two choices. One option is to specify the path by index (and it's a 1-based index) like so:

xyzzy 2

...which would switch to the directory that's the second item in the list. Or, I could leave off the index number and just do:

xyzzy

...to have it loop through each directory as I need. For more commands you can do, type:

xyzzy help

Of course, work is more fun with the silly echo statements that I added in.

Note that xyzzy is a reference to the Collosal Cave text adventure, where typing xyzzy would let you switch between two rooms in the game in order to avoid grues.

1
  • 2
    +1 I started playing with this. It's nice, but seems to fail if no directories have been added yet and you enter just xyzzy. Needs one more test for MAXLINES -eq 0 in cyclenext().
    – Joe
    Jun 4, 2016 at 5:34
5

I use a small script called z [link], which might also be of interest, even though it does not do exactly what you asked.

NAME
       z - jump around

SYNOPSIS
       z [-chlrtx] [regex1 regex2 ... regexn]

AVAILABILITY
       bash, zsh

DESCRIPTION
       Tracks your most used directories, based on 'frecency'.

       After  a  short  learning  phase, z will take you to the most 'frecent'
       directory that matches ALL of the regexes given on the command line, in
       order.

       For example, z foo bar would match /foo/bar but not /bar/foo.
2
  • Maybe I missed something, but where do I find the z script itself?
    – Joe
    Jun 4, 2016 at 5:01
  • Sorry, the link was on the z. Will edit to make it easier to see.
    – Graipher
    Jun 5, 2016 at 9:18
5

There is also cd_func from Petar Marinov, it's basically cd with a history of up to 10 entries: http://linuxgazette.net/109/misc/marinov/acd_func.html

# do ". acd_func.sh"
# acd_func 1.0.5, 10-nov-2004
# petar marinov, http:/geocities.com/h2428, this is public domain

cd_func ()
{
  local x2 the_new_dir adir index
  local -i cnt

  if [[ $1 ==  "--" ]]; then
    dirs -v
    return 0
  fi

  the_new_dir=$1
  [[ -z $1 ]] && the_new_dir=$HOME

  if [[ ${the_new_dir:0:1} == '-' ]]; then
    #
    # Extract dir N from dirs
    index=${the_new_dir:1}
    [[ -z $index ]] && index=1
    adir=$(dirs +$index)
    [[ -z $adir ]] && return 1
    the_new_dir=$adir
  fi

  #
  # '~' has to be substituted by ${HOME}
  [[ ${the_new_dir:0:1} == '~' ]] && the_new_dir="${HOME}${the_new_dir:1}"

  #
  # Now change to the new dir and add to the top of the stack
  pushd "${the_new_dir}" > /dev/null
  [[ $? -ne 0 ]] && return 1
  the_new_dir=$(pwd)

  #
  # Trim down everything beyond 11th entry
  popd -n +11 2>/dev/null 1>/dev/null

  #
  # Remove any other occurence of this dir, skipping the top of the stack
  for ((cnt=1; cnt <= 10; cnt++)); do
    x2=$(dirs +${cnt} 2>/dev/null)
    [[ $? -ne 0 ]] && return 0
    [[ ${x2:0:1} == '~' ]] && x2="${HOME}${x2:1}"
    if [[ "${x2}" == "${the_new_dir}" ]]; then
      popd -n +$cnt 2>/dev/null 1>/dev/null
      cnt=cnt-1
    fi
  done

  return 0
}

alias cd=cd_func

if [[ $BASH_VERSION > "2.05a" ]]; then
  # ctrl+w shows the menu
  bind -x "\"\C-w\":cd_func -- ;"
fi

Siimply use cd -- to show a list of the past up to 10 directories you cded to and cd -N (where N is the index of the entry) to go there.

4

I mostly use ZSH with oh-my-zsh profile. You can type into a terminal the following match:

# cd /ho

Then you can simply use arrows (up and down) to go through all the shell history which shows only those entries that start with the characters above. So, for instance, if you went to /home/morfik/Desktop/ and /home/morfik/something/, you can switch between the directories very fast. It doesn't matter how many entries in the shell history you have, but when you have a lot, just use a better expression, i.e. cd /home/morf and here press up/down arrows on you keyboard.

There's also another way to achieve the solution. In this case you have to use tmux and FZF. Then you just make a hotkey, for instance ctrl-r, and when you press it, your current window will be divided and you'll see this:

enter image description here

Now you can use expressions to search the list. I just typed ^cd /media, which returns only the entries that start with the phrase. Of course, you can just type 'cd 'home to match the command and also the entire directory name (not path, just name):

enter image description here

4

You can achieve this with a bunch of aliases in your ~/.bashrc (or equivalent)

The main goal is near-minimum typing (e.g. d5 jumps to directory number 5 in the pool) to switch between directories in the pool. Also we want to make it easy to add/remove directories to/from the pool:

alias pd=pushd
alias po=popd
alias d='dirs -v'
alias d0=d
alias d1='pd +1'
alias d2='pd +2'
alias d3='pd +3'
alias d4='pd +4'
alias d5='pd +5'
alias d6='pd +6'
alias d7='pd +7'
alias d8='pd +8'
alias d9='pd +9'
alias d10='pd +10'
# -- feel free to add more aliases if your typical dir pool is larger than 10

Now every time you push a directory on the stack it gets added to the numbered pool at position 0 (the current dir) and you can jump around (switch directories) using very little typing (d<N>) and you can view your numbered current pool at any time by just typing d.

A few examples of using these aliases:

Display the numbered dir-pool, (current dir is # 0)

$ d
 0  /tmp
 1  /
 2  /usr

Switch between dirs: use d<N>

$ d2
$ pwd
/usr

Add a new directory to the pool

$ pd /var/log
$ d
 0  /var/log
 1  /usr
 2  /tmp
 3  /

Jump around some more:

$ d3
$ pwd
/

$ d3
$ pwd
/tmp

$ d
 0  /tmp
 1  /
 2  /var/log
 3  /usr

Remove/pop the top (current) dir from the pool

$ po
$ d
 0  /
 1  /var/log
 2  /usr
2

If you have iselect installed, you could do something like this:

$ alias dirselect='cd $(iselect -a $(dirs -l -p | sort -u))'
$ dirselect

This will give you a full-screen ncurses-based interactive arrow-key navigable menu to select a directory to cd to.

If you haven't used pushd in the current shell session, the list of directories in the menu starts off with just one entry, your current directory. If there's only one entry, this dirselect alias will just cd to it without the menu screen, so it effectively does nothing (except prevent cd - from doing anything useful)

To add a new directory to the list, use pushd dir (or pushd -n dir to add a dir without cd-ing to it at the same time)

You can pre-populate the pushd stack by doing something like the following in your .bashrc or ~/.bash_profile:

for d in /var/tmp /tmp /path/to/somewhere/interesting ; do 
  pushd -n "$d" > /dev/null
done

You can remove entries with popd or popd -n.

See help pushd, help popd, and help dirs in bash for more info. And, of course, man iselect.

BTW, iselect is probably available pre-packaged for your distro. It is for Debian and Ubuntu etc, and probably for others too.

2

If you have 5 or 10 directories you use a lot, and don't necessarily care about recently used 5 or 10 directories, set up some command aliases like

alias cdw="cd /var/www/html" 

And so when I want to go to Apache home page directory I just type cdw as I answered in Equivalent of alias for a symbolic link?.

2

I am using jump to quickly change the working directory.

To add the current directory:

jump -a [bookmark-name]

To list all your bookmarks:

jump -l

e.g.:

------------------------------------------------------------------
 Bookmark    Path                                                 
------------------------------------------------------------------
 reports     ~/mydir/documents/reports
 projects    ~/documents/projects
 dl          ~/Downloads                     
------------------------------------------------------------------

Now you can easily jump to another directory:

jump reports

It supports autocompletion for bash and zsh.


Edit (in response to @Joe): the binary jump-bin is stored in /usr/local/bin, and with the bash integration script (in my PC located at /var/lib/gems/1.9.1/gems/jump-0.4.1/bash_integration/shell_driver) it creates a bash function jump which calls jump-bin.

1
  • Where does the jump command live? I don't have it and don't immediately see where to get it.
    – Joe
    Jun 4, 2016 at 5:08
2

I use alias to navigate. If you have few directories that are frequently accessed, then simply set aliases. For example,

alias e='cd /etc'
alias h='cd /home'
alias al='cd /var/log/apache2/'

Then simply

e 

will take you to /etc.

You must log in to answer this question.

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