[comp.sources.misc] directory stack manipulation routines for bourne shell

allbery@ncoast.UUCP (09/24/87)

Hello ( is there a better greeting that does not sound so boring? ) -

        Having been  working  for  a  long  time  under  csh  and  being
accustomed  to  the commands pushd and popd, I was saddened to find that
these commands were not avaiable under  my  version  of  Microport  Unix
System  V (version 2.2).  I had just come across ksh routines to do this
and thought, hey, what a wonderful way to spend a few hours  instead  of
doing  something that I should be doing (like real work 8-).  The result
is my first csh program (all other shell programs  where  written  using
the  bourne shell).  It is used with a few (four) alias-es and manages a
directory stack.  Hope you find it useful.

        If you decide to give this a try, wait  until  using  it  before
reading  the next sentence.  You shouldn't be reading this unless you've
tried it already - okay - experienced it? -  well  ...  it  may  not  be
incredibly fast, but the object was to learn a bit of the c shell.


                                                  Rickers
                                                  ..!drexel!rickers

==========c=u=t====h=e=r=e=====================c=u=t====h=e=r=e=========
#!/bin/csh
#
#	(-8 tab stops should be at every four for easy readibility 8-)
#
#	dirstack  --  c shell script to manipulate a directory stack
#					written by rickers, august 29, 1987
#
#	Permission is granted to do what you wish with this code.  Happily
#	placed in the public domain.  No copyrights - no nutin'.
#
#	Description of routines:
#		init  --  initializes the environment so that the routines can work.
#		dirs  --  prints out directory stack.
#		pushd --  push the current directory and change to that specified
#		popd  --  pop a directory and change to it
#
#	These routines pattern themselves after the same functions of the
#	C shell, with a few exceptions.  Idea to write this script taken from
#	ksh script originally written by David C. Stewart, and modified to
#	work under MKS tookkit ksh by Keith Ericson, both of Tektronix Inc.
#
#	To use, some aliases are needed.  These follow.
#		set dirstack=/usr/local/lib/dirstack
#		alias	initds		"set argv=(init \!*);  source $dirstack; set argv"
#		alias	dirs		"set argv=(dirs \!*);  source $dirstack; set argv"
#		alias	pushd		"set argv=(pushd \!*); source $dirstack; set argv"
#		alias	popd		"set argv=(popd \!*);  source $dirstack; set argv"
#		unset dirstack
#
#	This file should be placed in a known directory.  The dirstack variable
#	is just to make the alias look a bit neater.
#
#	You may be pondering with the question "why did he do it this way?"  when
#	i figure it out, i will let you know (maybe).  it just seemed the thing to
#	do.  note these need routines need to be executed using source.  it is 
#	faster	(it does not invoke another shell) and it makes the variable
#	available to the shell.
#	
#	Oh, btw, the other reason this was written, was to make up for a lacking
#	csh under Microport Unix System V 2.2.
#

#
#	Depending on argv[1], goto a certain spot in the script and execute 
#	
goto $argv[1]

init:
#	the current directory stack depth
	set ddepth=1

#	dstack is the directory stack, set to a max depth of 20
	set dstack=(`pwd` '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '')
	goto finish

#
#	dirs is equivalent (almost) to the csh command `dirs'.  It prints out
#	the stack, current directory first, followed by top of stack, and the
#	rest of the stack.  it fmt-s the output so it looks nice and does not
#	get fold-ed at 80 chars.  It is a bit slow - oh well, so are the rest
#	of the routines.
#
dirs:
	set i=$ddepth
	if ( $i == 1 ) set dstack[1]=`pwd`
	cp /dev/null /tmp/$$
	while ($i > 0)
		echo $dstack[$i] >> /tmp/$$
		@ i = $i - 1
	end
	sed "s,$home,\~,g" /tmp/$$ | fmt ; rm /tmp/$$
	goto finish

#
#	pushd is stretching from the csh definition of pushd.  given one arg,
#	it pushes the current directory on the stack, and changes to the
#	specified directory.  now comes the kludge.  to specify a buried
#	directory, use the syntax: pushd + n.  note the space between the
#	plus sign and the number.  It is there to make life easier and faster.
#	you wouldn't want it any other way now, would you?  also, the way it
#	works is different from the way my pushd works (under 4.2BSD).  It 
#	exchanges the current directory and the specified directory, making
#	the specified directory current.  note that the number is offset to
#	the current directory on the dirs print-out ( + 1 is top of stack ).
#	also note that the current directory is not on the top of the stack.
#	pushd given with no arguments is the exact same as pushd + 1, and 
#	exchanges the current directory with the top to the stack.
#
pushd:
	switch ($#argv)
	case 1:
		if ( $ddepth < 2 ) then
			echo pushd: No other directory.
			goto finish
		else
			@ i = $ddepth - 1
			set temp=$dstack[$i]
			set dstack[$i]=$dstack[$ddepth]
			set dstack[$ddepth]=$temp
			cd $dstack[$ddepth]
		endif
		breaksw
	case 2:
		if ( $ddepth == 1 ) set dstack[1]=`pwd`
		@ ddepth++
		cd $argv[2]
		set dstack[$ddepth]=`pwd`
		breaksw
	case 3:
		@ i = $argv[3]
		@ i = $ddepth - $i
		set temp=$dstack[$i]
		set dstack[$i]=$dstack[$ddepth]
		set dstack[$ddepth]=$temp
		cd $dstack[$ddepth]
		breaksw
	default:
		echo usage: pushd \| pushd name \| pushd +n
		goto finish
	endsw
	goto dirs

#
#	popd is stretching from the csh definition of popd.  given no args,
#	it pops one directory from the stack and makes it current, assuming
#	that one exists.  again a kludge (same one).  to specify a buried
#	directory, use the syntax: popd + n.  note the space between the
#	plus sign and the number.  same old argument as above.  but, ha, this
#	one actually works like the csh popd.  oh well, there goes the large
#	deviation.  what this action does is removes the specifed directory
#	from the directory stack.  again, note that the number is offset to
#	the current directory on the dirs print-out ( + 1 is top of stack ).
#	also note that the current directory is not on the top of the stack,
#	although it is the first printed in the dirs output.
#
popd:
	if ( $ddepth == 1 ) then
		echo popd: Directory stack empty.
		goto finish
	else switch ($#argv)
	case 1:
		breaksw
	case 2:
		echo usage: popd \[ + n \]
		goto finish
		breaksw
	case 3:
		if ( ($argv[3] < 1) || ($argv[3] >= $ddepth) ) then
			echo popd: Illegal value.
			goto finish
		endif
		@ i = $ddepth - $argv[3]
		while ( $i < $ddepth )
			@ temp = $i + 1
			set dstack[$i]=$dstack[$temp]
			@ i = $i + 1
		end
		breaksw
	endsw
	endif
	@ ddepth--
	cd $dstack[$ddepth]
	goto dirs

#
#	finishup is the generic get outta here routine.
#	
finish:
	unset i
	unset temp

allbery@ncoast.UUCP (09/29/87)

In article <4563@ncoast.UUCP> rickers@RUTGERS.EDU@drexel.UUCP (Rick Wargo) writes:
>        Having been  working  for  a  long  time  under  csh  and  being
>accustomed  to  the commands pushd and popd, I was saddened to find that
>these commands were not avaiable under  my  version  of  Microport  Unix
>System  V (version 2.2).

Sure they are!  But you need to set them up yourself as shell functions.
I use something like the following (actually mine is much more elaborate,
supporting "myx" layer banners etc., and exploits our sh's "builtin"
command to allow redefining "cd" as a function):

DIRSTACK=...	# for pushd, popd, swapd
PREVDIR="$HOME"	# for backd

if [ -z "$HOST" ]
then	HOST=`uname` export HOST
fi

backd(){ ch "$PREVDIR"; echo `pwd`; }

# Following should be "cd", but you need "builtin" for that.
ch(){
	PREVDIR="$CWD"
	if [ $# -lt 1 ]
	then	cd
	else	cd "$1"
	fi
	CWD=`pwd` export CWD
	# exported so interactive subshells can ch $CWD to outwit symbolic links
	if [ "$CWD" = "$HOME" ]
	then	PS1="$HOST"
	else	PS1="$HOST":`echo "$CWD" | sed -e "s!^$HOME!~!"`
	fi
	PS1="$PS1"'$ '
}

dirs(){
	if [ "$DIRSTACK" != "" ]
	then
		echo "$CWD $DIRSTACK"
	fi
}

popd(){
	set $DIRSTACK
	if [ $# -ge 2 ]
	then	DIRSTACK="$*"
		ch $1
		set $DIRSTACK	# ch clobbered $*
		shift
		DIRSTACK="$*"
	fi
	set --
}

pushd(){
	DIRSTACK=$CWD" $DIRSTACK"
	if [ $# -lt 1 ]
	then	set $HOME
	fi
	if ch $1
	then	echo $DIRSTACK
	else	popd
	fi
	set --
}

swapd(){
	DIRSTACK=$CWD" $DIRSTACK"
	set $DIRSTACK
	if [ $# -ge 3 ]
	then	DIRSTACK="$*"
		ch $2
		set $DIRSTACK	# ch clobbered $*
		DIRSTACK="$1"
		shift 2
		DIRSTACK=$DIRSTACK" $*"
		echo $DIRSTACK
	else	shift
		DIRSTACK="$*"
		echo 'swapd: No previous directory' >&2
		set --
		return 1
	fi
	set --
}

readonly backd ch dirs popd pushd swapd

if [ "$CWD" ]
then	ch "$CWD"	# outwit symbolic links
else	ch "`pwd`"
fi