40

cd - can move to the last visited directory. Can we visit more history other than the last one?

13 Answers 13

31

The command you are looking for is pushd and popd.

You could view a practical working example of pushd and popd from here.

mkdir /tmp/dir1
mkdir /tmp/dir2
mkdir /tmp/dir3
mkdir /tmp/dir4

cd /tmp/dir1
pushd .

cd /tmp/dir2
pushd .

cd /tmp/dir3
pushd .

cd /tmp/dir4
pushd .

dirs
/tmp/dir4 /tmp/dir4 /tmp/dir3 /tmp/dir2 /tmp/dir1
  • 1
    There's also $OLDPWD in case you want to flip back and forth between two directories using the same command, but I'm unsure how shell-specific and distribution/kernel-specific this is. – mechalynx Sep 26 '14 at 18:07
  • 4
    @ivy_lynx OLDPWD exists in all POSIX shells, but it's useless for this question which asks how to go beyond that (the question already mentions cd - which is a shortcut for cd "$OLDPWD"). – Gilles Sep 26 '14 at 23:18
  • 2
    Is there a reason you use cd /tmp/dir1; pushd . instead of just pushd /tmp/dir1? – GnP Oct 3 '14 at 0:23
  • @gnp, no specific reason. It was just taken from the link that I have referred to in the answer. pushd /tmp/dir1 should work just fine. – Ramesh Oct 3 '14 at 0:29
  • 1
    Ok, just picked my curiosity. I'd like to suggest you improve your answer with an actual example using pushd and popd to traverse a directory tree back and forth. Your answer is already the correct one. – GnP Oct 3 '14 at 0:35
52

You didn't specify which shell you are using, so let this be excuse to advertise zsh.

Yes, we do have more history for cd, namely cd -2, cd -4 etc. Very convenient is cd -TAB, especially with completion system and colors enabled:

This is what I have in .zshrc:

setopt AUTO_PUSHD                  # pushes the old directory onto the stack
setopt PUSHD_MINUS                 # exchange the meanings of '+' and '-'
setopt CDABLE_VARS                 # expand the expression (allows 'cd -2/tmp')
autoload -U compinit && compinit   # load + start completion
zstyle ':completion:*:directory-stack' list-colors '=(#b) #([0-9]#)*( *)==95=38;5;12'

And the result:

enter image description here

  • 4
    bash bash bash bash – Tim Sep 26 '14 at 16:49
  • 7
    OK, I won't delete this answer, maybe will be useful for others. – jimmij Sep 26 '14 at 16:54
  • 23
    Unless the question specifically asks about bash, this is a valid answer. Don't remove it. – liori Sep 26 '14 at 17:21
  • What if the OP edits his question to include bash only, will this answer still be valid? – Ooker Sep 26 '14 at 21:57
  • 3
    It might bear mentioning that apart from setopt AUTO_PUSHD, none of the above setup is required to get an omnipresent directory stack with completion in stock zsh. PUSHD_MINUS reverses the sense of cd + and cd - (a matter of taste), CDABLE_VARS is irrelevant to directory stacks, and the zstyle invocation given here simply adds colouring to the output of a directory stack completion. One must however initialise the completion subsystem with autoload -U compinit && compinit. – wjv Apr 7 '16 at 15:59
13

To answer your question regarding "more history". No the cd - feature in Bash only supports a single directory that you can "flip" back to. As @Ramesh states in his answer. If you want a longer history of directories you can use pushd and popd to save a directory or return to a previous one.

You can also see the list of what's currently in the stack with the dirs command.

A detailed explanation can be found from this answer titled: How do I use pushd and popd commands?.

  • I have included a reference from your answer. Hope you don't mind. :) – Ramesh Sep 26 '14 at 16:19
  • @Ramesh - nope go ahead. – slm Sep 26 '14 at 16:19
8

You can install and use my dirhistory utility for bash.

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).

7

You have as much history as you want:

cd() {
[ "$((${DIRSTACKMAX##*[!0-9]*}0/10))" -gt 0 ] &&
        set -- "$@" "$DIRSTACK"               &&
        DIRSTACK='pwd -P >&3; command cd'     ||
        { command cd "$@"; return; }
_q()    while   case "$1" in (*\'*) :   ;;      (*)
                ! DIRSTACK="$DIRSTACK '$2$1'"   ;;esac
        do      set -- "${1#*\'}" "$2${1%%\'*}'\''"
        done
while   [ "$#" -gt 1 ]
do      case    ${1:---} in (-|[!-]*|-*[!0-9]*) : ;;
        (*)     eval "  set $((${1#-}+1))"' "${'"$#}\""
                eval '  set -- "$2"'" $2"'
                        set -- "${'"$1"'}" "$1"'
        ;;esac; _q "$1"; shift
done
eval "  DIRSTACK=; $DIRSTACK    &&"'
        _q "$OLDPWD"            &&
        DIRSTACK=$DIRSTACK\ $1
        set "$?" "${DIRSTACK:=$1}"'" $1
"       3>/dev/null
[ "$(($#-1))" -gt "$DIRSTACKMAX" ] &&
        DIRSTACK="${DIRSTACK% \'/*}"
unset -f _q; return "$1"
}

That's a shell function that should enable any POSIX compatible shell to offer zsh-style cd history. It does all of its work without invoking a single subshell, and I believe its flow is pretty sound - it seems to handle all cases correctly under moderate testing.

The function attempts to play as nicely with its environment as it may while still relying on fully portable syntax - it makes only one assumption and that is that the $DIRSTACK environment variable is its property to do with as it will.

It canonicalizes all paths that it stores in $DIRSTACK and serializes all of them on single-quotes - though it ensures each is safely quoted and serialized before adding it to the value of the variable and shouldn't have any issue with any special characters of any kind. If the $DIRSTACKMAX environment variable is set it will use it as an upper limit for the number of paths it retains in history, else the limit is one.

If you load the function you just cd as normal but will also be able to do the cd -[num] for retracing back through your change directory history.

The function's primary mechanism is cd itself - and the ${OLD,}PWD environment variables. POSIX specifies that cd change these for every path move - and so this just uses the shell's builtin variables and saves the values for as long as you like.

  • @datUser - i knew someone had a dat. youre quite welcome – mikeserv Jan 24 '17 at 9:58
4

The acd_func.sh script does exactly what you describe. Essentially it overloads the cd function and enables you to type cd -- to get a list of previously visited directories, from which you can select by number. I find it very hard to use bash without this anymore, and its the first thing I install on a new system.

3

Others already covered some interesting solutions. Some time ago I created my own solution to a related problem that could be quickly modified to do "straight history". I basically wanted to "label" a few commonly used directories, and wanted all open shells to see them, and for them to persist between reboots.

#dir_labels
#functions to load and retrieve list of dir aliases

function goto_complete {
    unset dir_labels
    declare -A dir_labels
    {
    while read line; do
        ll_pre="${line%% *}"
        ll_dir="${line#* }"
        dir_labels["$ll_pre"]="$ll_dir"
    done
    } < ~/.dir_labels
    unset ll_pre
    unset ll_dir

    local cur possib
    cur="${COMP_WORDS[COMP_CWORD]}"
    possib="${!dir_labels[@]}"
    COMPREPLY=( $(compgen -W "${possib}" -- ${cur}) )
}

complete -F goto_complete goto

function goto {
    unset dir_labels
    declare -A dir_labels
    {
    while read line; do
        ll_pre="${line%% *}"
        ll_dir="${line#* }"
        dir_labels["$ll_pre"]="$ll_dir"
    done
    } < ~/.dir_labels
    unset ll_pre
    unset ll_dir

    if [ $# -gt 0 ]; then
    key="$1"
    else
    key=default
    fi
    target="${dir_labels[$key]}"
    if [ -d "$target" ]; then
    cd "$target"
    echo "goto $key: '$target'"
    else
    echo "directory '$target' does not exist"
    fi
}

function label {
    unset dir_labels
    declare -A dir_labels
    {
    while read line; do
        ll_pre="${line%% *}"
        ll_dir="${line#* }"
        dir_labels["$ll_pre"]="$ll_dir"
    done
    } < ~/.dir_labels
    unset ll_pre
    unset ll_dir

    if [ $# -gt 0 ]; then
    target="$1"
    else
    target="default"
    fi
    dir_labels["$target"]=$PWD
    for i in "${!dir_labels[@]}"; do
    echo "$i ${dir_labels[$i]}"
    done > ~/.dir_labels
}

Basically I'd just do label foo to call the current directory foo, and then from whatever shell, goto foo whould cd directly there. Empty argument: label would create a default target for goto.

I didn't bother implementing automated removal of aliases, but otherwise, I'm still using this in a slightly modified form.

2

You can use my "cd history" function from http://fex.belwue.de/fstools/bash.html

It remembers every directory where you have been and with "cdh" you will see a list of the last 9 directories. Just enter the number and you are back in this directory.

Example:

framstag@wupp:/: cdh
1: /usr/local/bin
2: /var
3: /
4: /tmp/135_pana/1280
5: /tmp/135_pana
6: /tmp/weihnachtsfeier
7: /tmp
8: /local/home/framstag
select: 4
framstag@wupp:/tmp/135_pana/1280:

cdh works with autocd aka "cd without cd": you do not have to type cd or pushd.

2

I'd like to recommend my extended 'cd' function to you:

https://github.com/dczhu/ltcd

enter image description here

It provides the following features to make life easier:

  • Global dir listing, which shows recently visited dirs from all terminal tabs/windows.
  • Local dir listing, which is local to current shell session.
  • Both listings support quick navigation by using j/k (go down/up), numbers, and word searching.
  • Global free jumping (e.g. "cd dir" or "cd ar" to go to /path/to/foo/bar/directory/).
  • Hm, looks promising. I'll check it out. – ddnomad Apr 2 '17 at 19:36
1

for bash, basically: instead of using cd use pushd to change directorys, so they are saved (meaning stacked)

pushd /home; pushd /var; pushd log

To see the stack use dirs and for easier navigation (to get the numbers of the "stack-entries" use:

dirs -v

Output:

me@myhost:/home$ dirs -v
 0  /home
 1  /var
 2  /tmp

Now utilize these numbers with cd and ~ like:

cd ~1

But now these numbers are rearranged now and position "0" will change, so just pushd the directory to the top position twice (or use a dummy on position 0) like:

me@myhost:/home$ dirs -v
 0  /home
 1  /home
 2  /var
 3  /tmp

now 1..3 will keep there position I read this somewhere but do not know anymore, so sorry for not giving credit

(to release the current directory from the stack/deleting it from history use popd)

1

See the cdh function in "Shell Programming, 4e" on page 312. It keeps the history in an array.

Here's a more advanced version: https://drive.google.com/open?id=0B4f-lR6inxQWQ1pPZVpUQ3FSZ2M

It Stores the history in the file CDHISTFILE and allows changing to the most recent directory that contains a string, e.g.,

cd -src

It installs itself over the existing cd command by doing an alias cd=_cd

1

Just wanted to add fzf-marks as a possible solution.

Once installed it gives you the commands mark and jump to add and search for bookmarked directories (yes, the it's not exactly the complete history, only the ones you bookmarked yourself).

The problem I have with pushd/popd it session specific behaviour, i.e. I'd like to have the same stack on different bash session or so which is possible for fzf-marks.

0

I tried the answer @mikeserv gave, but it didn't quite work for me. I couldn't figure out how to fix it, so I just wrote my own:

cd() {
    # Set the current directory to the 0th history item
    cd_history[0]=$PWD
    if [[ $1 == -h ]]; then
        for i in ${!cd_history[@]}; do
            echo $i: "${cd_history[$i]}"
        done
        return
    elif [[ $1 =~ ^-[0-9]+ ]]; then
        builtin cd "${cd_history[${1//-}]}" || # Remove the argument's dash
        return 
    else
        builtin cd "$@" || return # Bail if cd fails
    fi
    # cd_history = ["", $OLDPWD, cd_history[1:]]
    cd_history=("" "$OLDPWD" "${cd_history[@]:1:${#cd_history[@]}}")
}

This is also available as a GitHub Gist. To use this, just paste the function into your .bashrc or similar, and you'll be able to do things like cd -5 to go back to the 5th last directory you've been in. cd -h will give you an overview of your history.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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