Skip to content

Instantly share code, notes, and snippets.

@terrycojones
Created March 18, 2011 03:01
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save terrycojones/875548 to your computer and use it in GitHub Desktop.
Save terrycojones/875548 to your computer and use it in GitHub Desktop.
A shell function for changing directories
#
# c - bash directory changing functions that maintain a
# most-recently used stack.
#
# Run with -help as an argument to see invocation options, or find the
# d_usage function below.
#
# To use these functions, store this in a file someplace and
# then execute
#
# . filename
#
# where 'filename' is the file you stored this stuff in. You can then use c
# to change directories instead of cd. You'll probably the above line into
# your ~/.bash_profile if you like what these functions do for you.
#
# There's undoubtedly many shortcomings in the following. Changing dirs can
# be complicated. E.g., this will surely break if you have directories
# with whitespace in their names. That may not be too hard to fix, but I
# never do that, so I haven't worried about it. Sorry.
#
# Make sure you have a version of expr that knows how to do regex matches.
# Either via expr match str regex or expr str : regex. The code below uses
# the : form as it's older, and works on the expr that ships with Mac OS X
# and Ubuntu. If your expr can't handle : matches, get hold of the latest
# GNU sh-utils package and install that. It's a good idea to install it
# anyway, since some stock versions of expr are broken and will cause the
# below functions to break occasionally (e.g., Solaris 2.5.1 /usr/bin/expr
# gives a syntax error on expr match /usr / ).
#
# Terry Jones (terry@jon.es)
# Jan 2, 1998 (Modified Mar 17, 2011)
#
d_trunc_def=10
d_version=1.02
d_date='Jan 3, 1998'
function c() {
case $# in
0)
case "$d_last_call_time" in
'')
d_last_call_time=$SECONDS
d_show_dirs
return
;;
esac
local s
declare -i s=$SECONDS
if [ $[ $d_last_call_time + 1 ] -ge $s ]
then
# It's a double-d
d_last_call_time=$s
case "$my_dir_names" in
'')
my_dir_names="$HOME"
builtin cd
return
;;
esac
d_cd_match $HOME 1
return
fi
d_show_dirs
d_last_call_time=$s
;;
1)
case $1 in
-)
d_cd_n 2
return
;;
-c)
my_old_dir_names="$my_dir_names"
my_dir_names=
return
;;
-r)
my_dir_names="$my_old_dir_names"
d_show_dirs
return
;;
-s)
d_sort_dirs
d_show_dirs
return
;;
-t)
d_limit_n $d_trunc_def
d_show_dirs
return
;;
-v | -version | --version)
echo "d version $d_version ($d_date)"
return
;;
-help | --help)
d_usage
return
;;
.)
d_cd_match $PWD 1
return
;;
..)
case "$my_dir_names" in
'')
my_dir_names=`dirname $PWD`
builtin cd ..
return
;;
esac
set $my_dir_names
d_cd_match `dirname $1` 1
return
;;
[0-9]*)
d_cd_n $1
return
;;
*)
local target=$1
case $target in
/*) ;;
*)
if [ -d $target ]
then
target=$PWD/$target
fi
;;
esac
case "$my_dir_names" in
'')
my_dir_names="$target"
builtin cd $1
return
;;
esac
d_cd_match $target 0
return
;;
esac
;;
2)
case $1 in
-k)
case $2 in
[0-9]*) d_remove_n $2 ;;
*) d_kill_match $2 0 ;;
esac
;;
-t)
case $2 in
[0-9]*)
d_limit_n $2
d_show_dirs
return
;;
*)
d_usage
return 1
;;
esac
;;
*)
d_usage
return 1
;;
esac
;;
*)
d_usage
return 1
;;
esac
}
d_usage () {
local d=c
cat <<EOF
usage: $d [option] [. | - | dir | number | pattern]
$d (no args) = show dir stack. Twice in a row (quickly) takes you to \$HOME.
$d NUM = go to directory number NUM, and bring it to the stack top.
$d . = put the current directory at the top of the stack.
$d - = go to the previous directory in the stack.
$d PATTERN = go to the first directory in stack matching PATTERN.
$d DIR = go to directory DIR, and put it first on the stack.
$d -c = clear the directory stack.
$d -k NUM = kill (remove) directory number NUM in the stack.
$d -k PATTERN = kill (remove) the first directory in stack matching PATTERN.
$d -r = restore the directory stack to the last pre-clear state.
$d -s = sort the directory stack.
$d -t [NUM] = truncate the stack to top NUM elements (default $d_trunc_def).
EOF
}
d_cd_n () {
case "$my_dir_names" in
'') echo 'No $my_dir_names directories are set'; return;;
esac
local want
declare -i want=$1
set $my_dir_names
if [ $want -gt $# ]
then
echo "We only have $# dirs in \$my_dir_names"
return
fi
local save=
while [ $want -gt 1 ]
do
save="$save $1"
shift
want="$want - 1"
done
local goto=$1
shift
my_dir_names="$goto $save $*"
echo $goto
builtin cd $goto
}
d_show_dirs () {
case "$my_dir_names" in
'') return;;
esac
local i
declare -i i=1
set $my_dir_names
while [ $# -ne 0 ]
do
echo "$i) $1"
i="$i + 1"
shift
done
}
d_cd_match () {
# Precondition: $my_dir_names is not empty
local target=$1
local exact=$2
set $my_dir_names
if [ $1 = $target ]
then
# We're there already.
return
fi
local pre=
local post=
case $exact in
0) pre=.\\\* ;;
1) post=\\\$ ;;
esac
shift # We ignore the first dir, since we're in that dir already.
local n
declare -i n=2
while [ $# -ne 0 ]
do
case `eval expr $1 : $pre$target$post` in
0)
shift
n="$n + 1"
;;
*)
d_cd_n $n
return
;;
esac
done
my_dir_names="$target $my_dir_names"
builtin cd $target
}
d_kill_match () {
case "$my_dir_names" in
'') echo 'No $my_dir_names directories are set'; return;;
esac
local target=$1
local exact=$2
set $my_dir_names
local pre=
local post=
case $exact in
0) pre=.\* ;;
1) post=\$ ;;
esac
local n
declare -i n=1
while [ $# -ne 0 ]
do
case `expr $1 : $pre$target$post` in
0)
shift
n="$n + 1"
;;
*)
d_remove_n $n
return
;;
esac
done
}
d_remove_n () {
# Precondition: $my_dir_names is not empty
local n
local save=
declare -i n=$1
set $my_dir_names
while [ $n -gt 1 ]
do
save="$save $1"
shift
n="$n - 1"
done
shift
my_dir_names="$save $*"
d_show_dirs
}
d_limit_n () {
local n
local save=
declare -i n=$1
case "$my_dir_names" in
'') return ;;
esac
set $my_dir_names
if [ $# -le $n ]
then
return
fi
while [ $n -gt 0 -a $# -gt 0 ]
do
save="$save $1"
shift
n="$n - 1"
done
my_dir_names="$save"
}
d_sort_dirs () {
case "$my_dir_names" in
'') return ;;
esac
local tmp=/tmp/d_$$_
rm -f $tmp || return
set $my_dir_names
while [ $# -gt 0 ]
do
echo "$1"
shift
done | sort > $tmp
my_dir_names="`cat $tmp`"
rm -f $tmp
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment