[net.sources] Korn Shell LRU Directory History

df@nud.UUCP (03/26/87)

With the recent discussion on Korn Shell aliases for cd, I thought I'd
post my own cd alias.  It maintains a directory history in least
recently used (LRU) order.  You can print the directory history
and cd to a recent directory via number or pattern matching.
I much prefer it to pushd/popd since the history occurs automatically
through the cd command.
Optionally, all your shells can share the same directory via a history
file, though there is a slight performance hit for this particular feature.

Here is an example of its use.
==============================================================
	$ pwd
	/usr/df
	$ cd /
	$ cd /bin
	$ cd /usr/bin
	$ cd -l			# "cd -l" lists directory history
	  3 /usr/df
	  2 /
	  1 /bin
	  0 /usr/bin
	$ cd -3			# "cd -3" cd's to third most recent directory
	/usr/df
	$ cd -l
	  3 /
	  2 /bin
	  1 /usr/bin
	  0 /usr/df
	$ cd -bin		# "cd -foo" cd's to most recent directory name
	/usr/bin		# 		containing the string "foo"
	$ cd -l
	  3 /
	  2 /bin
	  1 /usr/df
	  0 /usr/bin
	$ 
==============================================================

From $HOME/.profile:
==============================================================
export CDHISTFILE CDHIST PWD
CDHIST=$HOME
# CDHISTFILE=$HOME/.cdhistory	# set this for directory history file.
CDHISTFILE=
ENV=$HOME/.kshrc
==============================================================

From $HOME/.kshrc (Korn shell ENV file):
==============================================================
alias set_prompt="PS1='$ '"	# of course, my real prompt is different.
alias cd='. $HOME/.functions; _cd'
alias md='. $HOME/.functions; md'
==============================================================

From $HOME/.functions (Where my function definitions reside):
==============================================================
alias cd=_cd
function _cd
{
	typeset -i cdlen i
	typeset t

	if [ $# -eq 0 ]
	then
		set -- $HOME
	fi

	if [ "$CDHISTFILE" -a -r "$CDHISTFILE" ] # if directory history exists
	then
		typeset CDHIST
		i=-1
		while read -r t			# read directory history file
		do
			CDHIST[i=i+1]=$t
		done <$CDHISTFILE
	fi

	if [ "${CDHIST[0]}" != "$PWD" -a "$PWD" != "" ]
	then
		_cdins				# insert $PWD into cd history
	fi

	cdlen=${#CDHIST[*]}			# number of elements in history

	case "$@" in
	-)					# cd to new dir
		if [ "$OLDPWD" = "" ] && ((cdlen>1))
		then
			print ${CDHIST[1]}
			'cd' ${CDHIST[1]}
		else
			'cd' $@
		fi
		;;
	-l)					# print directory list
		typeset -R3 num
		((i=cdlen))
		while (((i=i-1)>=0))
		do
			num=$i
			print "$num ${CDHIST[i]}"
		done
		return
		;;
	-[0-9]|-[0-9][0-9])			# cd to dir in list
		if (((i=${1#-})<cdlen))
		then
			print ${CDHIST[i]}
			'cd' ${CDHIST[i]}
		else
			'cd' $@
		fi
		;;
	-*)					# cd to matched dir in list
		t=${1#-}
		i=1
		while ((i<cdlen))
		do
			case ${CDHIST[i]} in
			*$t*)
				print ${CDHIST[i]}
				'cd' ${CDHIST[i]}
				break
				;;
			esac
			((i=i+1))
		done
		if ((i>=cdlen))
		then
			'cd' $@
		fi
		;;
	*)					# cd to new dir
		'cd' $@
		;;
	esac

	_cdins					# insert $PWD into cd history

	if [ "$CDHISTFILE" ]
	then
		cdlen=${#CDHIST[*]}		# number of elements in history

		i=0
		while ((i<cdlen))
		do
			print -r ${CDHIST[i]}	# update directory history
			((i=i+1))
		done >$CDHISTFILE
	fi
	set_prompt
}

function _cdins					# insert $PWD into cd history
{						# meant to be called only by _cd
	typeset -i i

	((i=0))
	while ((i<${#CDHIST[*]}))		# see if dir is already in list
	do
		if [ "${CDHIST[$i]}" = "$PWD" ]
		then
			break
		fi
		((i=i+1))
	done

	if ((i>22))				# limit max size of list
	then
		i=22
	fi

	while (((i=i-1)>=0))			# bump old dirs in list
	do
		CDHIST[i+1]=${CDHIST[i]}
	done

	CDHIST[0]=$PWD				# insert new directory in list
}
==============================================================

Cd is a sizable alias, but actually executes quite quickly.
I hope it is useful to someone else; I've been using it for several months.

-Dale

-- 
	seismo!noao!mcdsun!nud!df	602/438-5739	ihnp4!mot!nud!df

ajs@hpfcdt.UUCP (03/31/87)

# Here's yet another way to remember and re-use old directories.  This one
# remembers everywhere you've been during a login session, and lets you
# pick from a sorted menu ("mcd") or list a Pareto-style summary ("lcd").
# I've found it only occasionally useful, but fast and painless.  If
# nothing else you might find these functions educational.

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by ajs at hpfcdt on Mon Mar 30 16:49:13 1987
#
# This archive contains:
#	ksh.cd	
#
# Error checking via wc(1) will be performed.

echo x - ksh.cd
cat >ksh.cd <<'@EOF'

# ksh functions to remember directory changes and let you pick (from a menu)
# directories to which to cd.

# Usage:
# Read this file with ". file", or put it in your .profile or .kshrc.  Then
# cd as usual.  Say "mcd" to get a menu of places you've been, to pick from.
# Say "lcd" to list your cd history, most-visited first.

# Warning:  Some things don't work right due to ksh deficiencies:
#
# - The typeset -x below is intended to cause DIRHIST[] to be exported to
#   subshells.  But ksh only exports the first element of any array (and
#   that's clobbered when mycd() is declared).  Too bad.
#
# - For some reason, the quotes around "$@" in the select in mcd() cause menu
#   item 1 to be munged sometimes, reason unknown.  Remove the quotes if this
#   is a problem and you never visit a directory with a blank or tab in its
#   name.


# INITIALIZE:
#
# Note that during .profile, $PWD is not set yet.

	if [ -z "${DIRHIST:-}" ]		# not already set.
	then
	    DIRHIST="${PWD:-$HOME}"
	fi

	typeset -x DIRHIST
	typeset -x DIRCOUNT=1		# just for fun, how often before.


# FUNCTION mycd():  DO A CD AND REMEMBER ALL DIRS VISITED:

	alias cd=mycd			# alias > built-in > function.

	mycd()
	{
	    typeset -i dirat=0		# current saved dir index; local var.
	    typeset -i dirs=${#DIRHIST[*]}
					# number of entries in "global" array.

	# Do the change:

	    if 'cd' "${@-$HOME}"	# call shell built-in.
	    then			# only if it succeeded.

	# See if new directory name (note, full name) is already known:

		while [ $dirat -lt $dirs ]
		do
		    if [ "${DIRHIST[$dirat]}" = "$PWD" ]
		    then
			break
		    fi

		    (( dirat=dirat + 1 ))
		done

	# Save new, or tell count for old:

		if [ $dirat -ge $dirs ]
		then
		    DIRHIST[$dirat]="$PWD"
		    DIRCOUNT[$dirat]=1
		    echo "(first time here)"
		else
		    echo "(previous: ${DIRCOUNT[$dirat]})"
		    (( DIRCOUNT[$dirat]=${DIRCOUNT[$dirat]} + 1 ))
		fi
	    fi

	} # mycd().


# FUNCTION mcd():  DO A CD CHOOSING FROM A MENU:

	mcd()
	{
	    set -- "${DIRHIST[@]}"	# copy to positional parameters.
	    set -s			# sort them.  (remove if not desired)

	    select dir in "$@"		# choose one.
	    do
		if [ "${dir-}" ]	# valid response.
		then
		    echo "+ cd $dir"
		    mycd "$dir"		# use function for side-effects.
		else
		    echo "cd: no change"
		fi

		break
	    done

	} # mcd().


# FUNCTION lcd():  LIST CD HISTORY:

	lcd()
	{
	    typeset -i dirat=0		# current saved dir index; local var.
	    typeset -i dirs=${#DIRHIST[*]}	# entries in "global" array.

	    while [ $dirat -lt $dirs ]
	    do
		echo "${DIRCOUNT[$dirat]}\t${DIRHIST[$dirat]}"
		(( dirat=dirat + 1 ))
	    done | sort +0nr -1

	} # lcd().
@EOF
if test "`wc -lwc <ksh.cd`" != '    114    444   2687'
then
	echo ERROR: wc results of ksh.cd are `wc -lwc <ksh.cd` should be     114    444   2687
fi

chmod 444 ksh.cd

exit 0