From: dattier@ddsw1.mcs.com (David W. Tamkin)
Subject: Ron Heiby's ksh pushd and popd
Organization: Contributor Account at ddsw1, Chicago, Illinois  60657
Date: Fri, 24 Jul 1992 19:12:46 GMT
X-Disclaimer:  Material posted in this article is the sole responsibility of
                the poster and does not represent MCSNet or the system owners.

The subject of pushd and popd in ksh came up in this newsgroup a few weeks
back, and I just discovered the following old post from Ron Heiby in my
files.  Naturally, I didn't remember it beforehand nor come across it until
I'd written my own routines.  Oh well.  Mr. Heiby's post follows.

[The rest of this article is by Ron Heiby; please fix your attributions
 if you follow up to it.  --DWT]
___________

If my records are complete, I posted this article in December of
1986, while working for AT&T.  Then, with modifications in December of
1987, while working for Motorola.  Since then, Ksh-88 has been
released and I've seen quite a few articles asking to do lots of stuff
with the prompt string.  So, here it is again, modified for Ksh-88 a
bit and enhanced in some other minor ways.  If you are still using an
older version of Ksh, UPGRADE!  Enjoy!

A while ago [from the perspective of someone writing in December of
1986], there were several messages posted to Usenet which showed ways
to include various information into a Korn Shell PS1 prompt.  I
thought that a number of these were real clever and incorporated them
into my own environment.

Now that Motorola Microcomputer Division [now the Motorola Computer
Group] is selling the Korn Shell for our Delta Series of 68020-based
systems, I thought I'd share this generally.

Relatively little here is my own invention, but I can no longer say
who had every idea.  I did put a lot of work into integrating
everything together so that it would all work in a coordinated
fashion.  The time stamp stuff is from Korn.  The "autoload" stuff
[originally used] for functions (pshd, popd, etc.) as well as the
original versions of those functions is from a Usenet article by Avi
E. Gross in 10/84.  [This has since been absorbed in slightly
different form into standard Ksh-88.]  I have made some readability
and efficiency enhancements to the originals, as well as merging them
into the rest of the stuff here.  I pulled out the large blocks of
comments in Avi's original to improve the performance in my running
copy and keep the original intact elsewhere so I can figure out what's
going on.  As to the prompt-resetting code, I know there were at least
three different people (plus me) who contributed to what is merged
together here, but I lost the original Usenet articles.  Thanks,
whoever you are.

I've tried to put this is into a form that you can easily install into
your own environment.  I mostly did it to see if I could.  Since then,
it has proved useful enough for me to keep it as my normal
environment.

One could easily turn up one's nose at all this, if one really likes the "$ "
prompt, as this is about as far from that as you can get.  The normal prompt
when logged in and in your home directory is:   host: hh:mm:ss [~]
                                                nn>
After "cd /usr/src/cmd/vi", it is:              host: hh:mm:ss [src/cmd/vi]
                                                nn>
After "pshd /usr", it is:                       host: hh:mm:ss 1: [/usr]
                                                nn>
After "popd", it is back to:                    host: hh:mm:ss [src/cmd/vi]
                                                nn>
After invoking a sub-shell (ksh):               host: hh:mm:ss [src/cmd/vi]
                                                nn>>

The "host" is the output of the "uname" command.  I added the host
name to the prompt to make it more obvious to whom I was "talking" in
the face of "cu" and "rlogin" and windowing terminals.  The "nn" is
the ksh history number.  hh:mm:ss is the time.  As you can see, up to
the last three directories in your current directory are included.  If
you are in a subdirectory of your home directory, your home directory
path counts as a single directory and is represented with a tilde (~).
If you are using Shell Layers (shl), the layer name "foo" or number
"(n)" is displayed as part of the prompt, too.  If the uid of the user
is 0 (root), then a pound sign (#) is used instead of a greater-than
(>) to indicate nesting of sub-shells.  The prompt was split into two
lines because the escape sequences to highlight a non-zero return code
get counted as printable characters and so fewer characters from the
command you are entering are visible in the line.

Good luck trying to change any of it without breaking something else.
I am currently running ksh "Version 11/16/88e".

Even if you don't use this, it makes an interesting exercise to figure
out how everything works.  After doing so, you'll have a very good
understanding of ksh programming.

Ron.

--------- This goes in your .profile --------
#       environment for fancy prompt
export PSSHL PS1="!$ " FPATH=~/ksh

-------- This goes in your $ENV file ---------
# All this good stuff needed for interactive shells only.
# Guard it with appropriate tests for better effeciency of startup.
# Here's a good example of this:
#       case "$-" in
#       *c*)    # Stuff for -c, command line shells goes here.
#               :
#               ;;
#       *)      # Stuff for for non -c, (interactive) shells.
#               :
#               ;;
#       esac
#
#               Set prompt string portion for Shell Layers layer.
TTY=$(tty)
if [ "$TTY" != "${TTY#*sxt}" -a "${SHDEPTH:-}" = ">" ]
then
        export PSSHL=$PS1
fi
unset TTY
#
#               Set System name string for fancy prompt
UNAME=$(uname)
#
#               Set standout strings for fancy prompt
: ${SMSO:=$(tput smso)} ${RMSO:=$(tput rmso)}
#
#               Set up for time stamping
typeset -RZ2 _x1 _x2 _x3
let SECONDS=$(date '+3600*%H+60*%M+%S')
TIME='"${_d[(_x1=(SECONDS/3600)%24)==(_x2=(SECONDS/60)%60)==(_x3=SECONDS%60)]}$_x1:$_x2:$_x3"'
#
#               Define the replacement for the "cd" builtin
function ch {
        if cd ${*:-''}
        then
                resetps1
        else
                return 1
        fi
}
alias cd=ch     # make it easy to use

function resetps1 {
        typeset _dir=$PWD
        typeset _dirt=${_dir#$HOME}     # directory tail
        if [ "${_dirt}" != "${_dir}" ]
        then
                _dir="~${_dirt}"
        fi
        typeset front=${_dir%/*/*/*}
        : ${front:=x}
        case ${pushlevel:-0} in
        0)      PS1="${UNAME}: ${TIME} ${PSSHL:-}${SMSO:-}\${?#0}${RMSO:-}[${_dir#$front/}]
!$SHDEPTH ";;
        *)      PS1="${UNAME}: ${TIME} $pushlevel: ${PSSHL:-}${SMSO:-}\${?#0}${RMSO:-}[${_dir#$front/}]
!$SHDEPTH ";;
        esac
}
# Functions related to push and pop routines
alias   dirs='echo "\t0: $PWD"' \
        popd='echo "You have never pushed any levels.\nYou remain at $PWD."'
#
#               Check for uid 0 (root)
id=$(id)
id=${id#uid=}
id=${id%%\(*}
if [ id -eq 0 ]
then
        export SHDEPTH=${SHDEPTH:-}'#'
else
        export SHDEPTH=${SHDEPTH:-}'>'
fi
# Set special colors
case $TERM in
mskermc)
        case $id in
        0)      color=37 ;;
        501)    color=31 ;;
        *)      color=35 ;;
        esac
        SMSO="[41;33m" RMSO="[44;${color}m[1m"
        unset color
        ;;
tm229|tm229g)
        case $id in
        0)      color=33 ;;
        501)    color=37 ;;
        *)      color=35 ;;
        esac
        SMSO="[41;33m" RMSO="[40;${color}m[1m"
        unset color
        ;;
*)
        ;;
esac
unset id
resetps1

-------- This is ~/ksh/pshd, linked to ~/ksh/pshpwd and ~/ksh/poppwd  ---------
unalias popd dirs
integer pushlevel=0

function pshd
{
    dirsave[pushlevel]="$PWD"           # save the name of the current dir
    promptsave[pushlevel]="$PS1"        # save the current prompt
    if [ pushlevel = 0 ]
    then
        pushprompt="$PS1"
    fi
    ch ${1:-$HOME} 2> /dev/null
    case $? in
      0)                                # "cd" succeeded.
        let pushlevel=pushlevel+1
        pwd                             # show the current directory
        : ${2:+`ls -Fx $3 $4 $5 $6 $7 $8 $9 >/dev/tty`}
        resetps1
        ;;
      *)                                # "cd" failed
        print "Can't change to $1, aborting"
        return 1
        ;;
    esac
}                                       # END of pshd

function popd
{
    integer levels=${1:-1}

    if (( pushlevel-levels>=0 ))
      then
        let pushlevel=pushlevel-levels
      else
        print "Can't pop that many levels. Your choices were:"
        dirs
        print "You are being returned to your original level"
        let pushlevel=0
    fi

    ch ${dirsave[$pushlevel]}
    pwd                 # show the user where they are.
    : ${2:+`ls -Fx $3 $4 $5 $6 $7 $8 $9 >/dev/tty`}
    PS1="${promptsave[$pushlevel]}"
}

function dirs
{
    integer level=$pushlevel
    integer lowest=$level-${1:-$level}
    if [ lowest -lt 0 ]
        then
            lowest=0
    fi
    print "     $level: $PWD"
    while [ level -gt lowest ]
    do
        let level=level-1
        print " $level: ${dirsave[$level]}"
    done
    unset level lowest
}

function pshpwd
{
print $PWD  "${@:-}" >~/.current-pwd
}

function poppwd
{
print $(<~/.current-pwd)
}

---- start of pshd family documentation ----
# In brief, this routine is called in the form of:
#       . ~/ksh/psh-pop.ksh name arg1 arg2 arg3 ...
# and has to do the following:
#               - Unalias all five related alias names.
#               - Declare all five function bodies using those names.
#               - Run the function that invoked this file, using the provided
#                 arguments.

# The first time one of these aliases is used, it needs to be "loaded". From
# then on, each one represents a loaded function, and runs smoothly. The
# advantages are that your .env is about a hundred lines smaller, and these
# functions are only defined when used.

# The first three routines are used to maintain and examine a directory stack
# of traversals through the file system. They are:
#       pshd [directory] [ls-indicator] [ls-arg] [ls-arg] [ls-arg]
#       popd [levels]    [ls-indicator] [ls-arg] [ls-arg] [ls-arg]
#       dirs [levels]

# These are different than other versions of pshd that have been posted to
# the net. Both pshd and popd have an optional first argument that tells
# which directory to go to, or how many levels to backtrack. If a second
# argument is specified (any string will do) then an "ls -C" is done after
# the action is completed. Any additional arguments are passed to ls. For
# example, "pshd /usr/spool/uucp l -l -a -t" will change the directory to
# /usr/spool/uucp and then do an "ls -C -l -a -t" to show full descriptions
# of all files in that directory in the orer that they were last changed.
# The "-C" argument is used to force our version of ls to columnate the
# output (even if it is going into a pipeline).

# The next two routines are used to share information about the current
# directory # between two windows on a DMD/BLIT or just to save the value
# for later. Only one value is currently supported. They are:
#       pshpwd [additional text]
#       poppwd
# Poppwd is often used in constructs like "cp *.c `poppwd`"

Ron Heiby mcdchg!heiby   Moderator: mod.newprod & mod.os.unix

Ron Heiby, heiby@chg.mcd.mot.com        Moderator: comp.newprod
"Wrong is wrong, even when it helps you." Popeye