Optimising directory navigation with multiple terminals

This simple shell snippet has saved me countless keystrokes and mental context switches. It works on the assumption that most tasks are based in or around a single directory such as a Git working tree.

s() {
        pwd > ~/.saved_dir
}

i() {
        cd "$(cat ~/.saved_dir)"
}

I typically use it like this:

$ cd /srv/long/path/that/is/tricky/to/autocomplete
$ ls
one   two   three
$ s

(Switch to another terminal tab or GNU Screen window, etc.)

$ pwd
/home/lamby
$ i
$ pwd
/srv/long/path/that/is/tricky/to/autocomplete
$ ls
one   two   three

This saves me having to navigate—or even think about navigating—to the deeply nested directory.

Comments (9)

Also useful, if you are using gnome-terminal: Right click ? New Terminal (which is the top entry, so can be selected quickly) opens a new terminal _in the same directory_. Not as flexible as your solution, of course, but still useful.

July 30, 2011, 10:18 a.m. #
Thanks Joachim. I use that too - all my keyboards even have a "Right click" button so it's very fast, and even avoids running then "i" step as you say.
GeorgeDanchev

Hi,
if you want to skip the autocompletion of the initial long path (cd /srv/long/path/that/is/tricky/to/autocomplete step) maybe you can find xd(1) useful asking it to navigate for you by the first letter of the nested directories in the whole path.

xd slp (slp.. as in /[s]rv/[l]ong/[p]ath)

if multiple alternatives are found, you will be presented a numbered list to choose from by hitting a single stroke.

July 30, 2011, 11:10 a.m. #
Sami Liedes

Might be that I'm confused, but shouldn't the 's' and 'i' operations be reversed under the "I typically use it like this", or their definitions reversed above it? As you defined them, 'i' saves the current directory and 's' restores it, but you seem to use them the other way round.

July 30, 2011, 12:26 p.m. #
You are absolutely right. Fixed.
Volans

Ctrl+Shift+t it's faster than right click and select ;)

In your usage example, the s() and i() functions are in reverse order, the i() function must be called first, to save the path, and then the s() function to change directory into that path.

The echo and the subshell in the i() function are useless, try: pwd > ~/.saved_dir

The cat and subshell in the s() function are useless, try: cd < ~/.saved_dir

Finally, why have you used bash functions instead of bash aliases?

Also a PS1 with a space before and after the path can help, allowing so to select the current path with a double click and pasting it everywhere ;)

July 30, 2011, 12:26 p.m. #
I have excised some other features I use which require the functions and subshells and I feel it is slightly easier to present here with functions. "cd < ~/.saved_dir" does not work as you claim.
Rob

Give the autojump package a try. I think you'll enjoy it.

July 30, 2011, 12:26 p.m. #
tshirtman

cd -

pushd/popd

there are already a few way to efficiently navigate between two or more directories for operations, without the need of another terminal…

July 30, 2011, 1:05 p.m. #
pushd/popd do not work across multiple terminals / GNU Screen windows. Doing everything inside a single terminal is wasteful when you have tiling window managers and multiple monitors.

A few other options:

1) pushd, popd
pushd `pwd`
cd /foo/bar
popd

2) use cd
cd /foo/bar
cd -

July 30, 2011, 4:34 p.m. #
Did you read any of the post or the preceding comments?
Kamraju

This tip is awesome. Thanks for sharing it. Also, the autojump package is totally awesome. Thanks Rob.

July 31, 2011, 12:11 a.m. #
Alan D. Salewski

I really like this idea, but now that I'm using it, one saved directory is not enough.

Below is what I currently have in my ~/.bashrc file. It creates the two default functions, which I consider my volatile scratch pad (shortest name, common case). It also generates functions with numeric "register" IDs in their names; these each work with a different saved directory file, so allows for working with multiple saved directories at the same time across shells.

Rather than 's()' and 'i()', I've named my functions 'r()' and 'j()', respectively, for mnemonic similarity to Emacs's `point-to-register' ( C-x r SPC ) and `jump-to-register' ( C-x r j ).

This idea could be made even more general (to allow for arbitrarily named registers on the fly, for instance), but the 9 additional "registers" are more than I need at the moment.

Thanks for your posting, Chris; I think this is a great idea.

if test -n "$PS1"; then

# These inspired by a blog by Chris Lamb:
#
# http://chris-l…
#
__ads_save_dir() {
local -r expected=2
if test $# -ne $expected; then
printf "${FUNCNAME}() error: expected %d args, but got %d\n" $expected $# 1>&2
return 1
fi
local which=$1
local where=$2
printf '%s\n' "${which}" > "${where}"
}

__ads_jumpto_dir() {
local -r expected=1
if test $# -ne $expected; then
printf "${FUNCNAME}() error: expected %d args, but got %d\n" $expected $# 1>&2
return 1
fi
local where_file=$1
test -f "${where_file}" || {
printf "${FUNCNAME}() error: no such file: %s\n" "${where_file}" 1>&2
return 1
}

read target_dir < "${where_file}"

# This prevents inadvertently changing to one's home directory (which is
# the behavior of 'cd' when invoked with no arg).
test -n "${target_dir}" || {
printf "${FUNCNAME}() error: no target directory path stored in file: %s\n" "${where_file}"
return 1
}

cd "${target_dir}"
}

# Functions named 'r' and 'j' for mnemonic similarity to Emacs's
# `point-to-register' ( C-x r SPC ) and
# `jump-to-register' ( C-x r j )
__ads_s_dir="${HOME}/.ads-saved-dir"
r() {
__ads_save_dir "$(pwd)" "${__ads_s_dir}"
}
j() {
__ads_jumpto_dir "${__ads_s_dir}"
}

# Generate rN and jN functions; these are just like our 'r()' and 'j()'
# functions defined above, except each works with a different "register".
for idx in $(seq 1 9); do
eval __ads_s${idx}_dir="${__ads_s_dir}-${idx}"

eval "r${idx}() { __ads_save_dir \"\$(pwd)\" \"\${__ads_s${idx}_dir}\"; }"
eval "j${idx}() { __ads_jumpto_dir \"\${__ads_s${idx}_dir}\"; }"
done
fi

Aug. 12, 2011, 10:37 a.m. #
Wow, nice.