#! /bin/bash -f
# Bob Camp     Cygnus Technical Consulting            7/1/1998

# This software and documentation is Copyright Bob Camp 1998
# <bob@hadron.demon.co.uk> and issued as freeware under the GNU General
# Public Licence.  It comes with absolutely no warranty.  It is used
# entirely at the user's own risk.  See the enclosed 'COPYING' file for
# full details.

shopt -s extglob

# Which tells you which program, man page etc is used.
version='V0.16.0'

# Find which executable, manual, include file etc will be used.
#
# Usage:
# which [-aAbcCdDeEfFghHiIlLmMnoOpP:qrRsStTUvVwWx:z0123456789] [--] [file [...]]


# Functions
# ---------

# Output an error message if not in quiet mode.

error_out()
{
  echo "$1" >&2
}

# Function to expand tilde '~' with the calling shell.  If the expansion
# fails then function returns original string.  Function value is true if
# the tilde was expanded and false otherwise.  Note bash always returns
# true.

expand_tilde()
{
  local x=$1 y ret=1
  if [[ "$x" != \~* ]]; then
    echo $x
    return 1
  fi
  if [[ "$x" == \~?(/*) ]] && [ "$Tilde" ]; then
    x=${x/#~/$Tilde}
    echo $x
    return 0
  fi
  if (( $native )); then
    set +f
    eval x=$x
    set -f
    echo $x
    return 0
  fi
  if (( ! $virtual )); then
    case $Shell_a in
    (sh)
      if y=`$Shellps -c "set -f; echo $x &2> /dev/null"` ; then
	x=$y
	ret=0
      fi
      ;;
    (bash)
      if [ "$OLDPWD" ]; then
	export OLDPWD
      fi
      if y=`$Shellps -c "set -f; echo $x &2> /dev/null"` ; then
	x=$y
	ret=0
      fi
      export -n OLDPWD
      ;;
    (csh)
      if y=`$Shellps -c "set noglob=0; echo $x |& $CAT"` ; then
	x=$y
	ret=0
      fi
      ;;
    (tcsh)
      if y=`$Shellps -c "set noglob=0; echo $x |& $CAT"` ; then
	x=$y
	ret=0
      fi
      ;;
    (ash) # Ash does not expand tilde.
      ;;   # Default action is correct.
    (ksh)
      if y=`$Shellps -c "noglob; echo $x &2> /dev/null"` ; then
	x=$y
	ret=0
      fi
      ;;
    (zsh)
      if [ "$OLDPWD" ]; then
	export OLDPWD
      fi
      if y=`$Shellps -c "set -f; echo $x 2> /dev/null"` ; then
	x=$y
	ret=0
      fi
      export -n OLDPWD
      ;;
    (*)
      error_all=2
      error_out "SORRY: Shell '$Shell_a' not implemented in expand_tilde(1)."
      ;;
    esac
  else
    case $Shell_a in
    (bash|zsh)
      if y=(set -f; echo $x &2> /dev/null) ; then
	x=$y
	ret=0
      fi
      ;;
    (sh|csh|tcsh|ksh)
      unset OLDPWD
      if y=(set -f; echo $x &2> /dev/null) ; then
	x=$y
	ret=0
      fi
      ;;
    (ash) # Ash does not expand tilde.
      ;;  # Default action is correct.
    (*)
      error_all=2
      error_out "SORRY: Shell '$Shell_a' not implemented in expand_tilde(2)."
      ;;
    esac
  fi
  echo $x
  return $ret
}

# Replace all instances of '//', '/./' with '/' and remove trailing '/' or
# '/.' and leading './'.  The function will correctly handle absolute and
# tilde paths.  The resulting path is echoed and the function returns true.

compact_path()
{
  local x=$1 y='' z='' ret=0
  case $x in
  (/*) z=/ ;;
  esac
  while [ "$y" != "$x" ]; do
    y=$x
    x=${x//\/\//\/}
    x=${x//\/.\//\/}
    x=${x%\/}
    x=${x%\/.}
    x=${x#.\/}
  done
  if [ "$x" = '.' -o  "$x" = '' ]; then
    x=$z
  fi
  echo $x
  return $ret
}

# This will remove the last segment, and its slash, from the path and echo
# the result.  The path is assumed compact.  Function returns true if it
# succeeded and false otherwise.  This is a bit like dirname on some
# systems but works correctly with '~name'.

chop_seg()
{
  local x=$1
  local ret=1
  case $x in
  (*/) ;; # Catches '/' or '~/' (path is compact remember).
  (*?/*) x=${x%/*} ; ret=0 ;;
  (/*) x=${x/%\/*/\/} ; ret=0 ;;
  (\~*) ;;
  (?*) x='' ; ret=0 ;;
  # Null case falls through.
  esac
  echo "$x"
  return $ret
}

# This function is really a poor man's basename.  It returns true if there
# was a last segment or false otherwise.

last_seg()
{
  local x=$1
  local ret=0
  x=${x##*\/}
  echo $x
  if [ ! "$x" ]; then
    ret=1
  fi
  return $ret
}

# Reduce the given path by compacting and then removing '/xxx/..' pairs.
# Note that if 'xxx' is a link then the '/xxx/..' pair will not be removed.
# This assumes that the path exists.  (Use bad_path if it does not.)  If
# the path does not exist then all '/xxx/..' pairs will be removed.

# Use this function as follows: result=`reduce_path your/path`.

# Function value is true '0' unless -n is specified and the resulting path
# is null.

# reduce_path [-n|-p] path

# Flags:
# -n Treat as a name: If result is a bare name prepend './'.
#                     Null is returned as null (function value is 1).
# -p Treat as a path: If result is null then return '.'.

reduce_path()
{
  local x pat='' i='' rem flag='' ret=0
  if (( $# == 1 )); then
    x=$1
  elif (( $# == 2 )); then
    x=$2
    case $1 in
    (-n) flag='name' ;;
    (-p) flag='path' ;;
    (*)
      error_all=2
      error_out "OOPS: Bad option ($1) given to reduce_path()."
      ;;
    esac
  else
    error_all=2
    error_out "OOPS: Wrong number of arguments ($#) given to reduce_path()."
  fi
  x=`compact_path $x`
  case $x in
  (/*) pat='/' ;;
  esac
  for i in ${x//\//\ } ; do
    rem=0
    # Need to expand tilde for testing link.
    if [ "$i" = '..' -a ! -L "`expand_tilde $pat`" ]; then
      # Possibly want to remove segment.
      case $pat in
      (*/..|..|*/|\~) ;; # Nope.
      (*/*) rem=1 ;; # Yep.
      (\~*) ;; # Nope.
      (?*) rem=1 ;; # Yep.
      (*) ;; # Nope.
      esac
    fi
    if (( $rem )); then
      pat=`chop_seg $pat`
    else
      case "$pat" in
      (/) pat="/$i" ;;
      (?*) pat="$pat/$i" ;;
      (*) pat="$i" ;;
      esac
    fi
  done
  case $flag in
  (name)
    case "$pat" in
    (*/*) ;;
    (*)
      if [ ! "$pat" ]; then
        ret=1
      else
        pat="./$pat"
      fi
      ;;
    esac
    ;;
  (path)
    if [ ! "$pat" ]; then
      pat='.'
    fi
    ;;
  esac
  echo $pat
  return $ret
}

# Walk down a given bad path and echo the first time it is bad (ie the
# shortest path).  Function returns true if it found a bad path and false
# otherwise.

bad_path()
{
  local x=$1 pat='' i='' ret=1
  case $x in
  (/*) pat='/' ;;
  esac
  for i in ${x//\//\ } ; do
    case "$pat" in
    (/) pat="/$i" ;;
    (?*) pat="$pat/$i" ;;
    (*) pat="$i" ;;
    esac
    # Need to expand tilde for testing path.
    if [ ! -e "`expand_tilde $pat`" ]; then
      ret=0 # Found bad path.
      break
    fi
  done
  echo $pat
  return $ret
}


# For all paths we report as given by user unless asked for type change.
# In these cases reduce the path.

# Convert given path to an absolute path and echo the result.  Use this
# function as follows: result=`absolute_path your/path`.  Function returns
# true for success and false otherwise.

absolute_path()
{
  local pat=$1
  case $pat in
  (/*) ;;
  (\~*) pat="`expand_tilde $pat`" ;;
  (*) pat=$Cwd/$pat ;;
  esac
  reduce_path $pat
}


# Convert given path to a relative (to $given or $Cwd) path and echo the
# result.  See reduce_path() for flags and return values.  For tilde path
# use 'relative_path path '~''.

# Use this function as follows: result=`relative_path your/path prep`

# relative_path [-n|-p] path reference_path

relative_path()
{
  local pat='' flag='' conv=0 dots='' pm p2='' pt=''
  case $1 in
  (-n|-p) flag=$1 ; shift ;;
  esac
  pat=$1
  if (( $# == 2 )); then
    if [ ! "$2" ]; then
      pm=$Cwd
    else
      p2=$2
      pm=`expand_tilde $p2`
    fi
  else
    pm=$Cwd
  fi
  case $pat in
  (/*) conv=1 ;;
  (~*) pat=`expand_tilde "$pat"` ; conv=1 ;;
  (*)
    if [ "$p2" ]; then
      pat="$Cwd/$pat"
      conv=1
    fi
    # Else already relative to '.'.
    ;;
  esac
  if (( $conv )); then
    # Simplify path to remove '..' etc, and append '/' for correct match
    # with glob character.
    pat=`reduce_path -p "$pat"`/
    while : ; do
      pt="$pm/"
      if [ "$pt" = "//" ]; then
        pt='/'
      fi
      case $pat in
      ($pt*) # Enough segments choped.
        pat=${pat#$pt}
        if [ ! "$pat" ]; then
          pat=$dots
        else
          pat=$dots$pat
        fi
        break # Exit the loop.
  	;;
      esac
      pm=`chop_seg $pm`
      dots="$dots../"
    done
  fi
  # Note $pat may be null here.
  if [ "$p2" ]; then
    pat="$p2/$pat"
  fi
  reduce_path $flag "$pat"
  return # Value of reduce_path().
}

# Unified by itself makes the path name the physical path.  It is a waste
# to maintain the present form when there is possibly a -A, -T or -R option
# also, so make it absolute here and convert back to original form if
# required.  This function assumes the given path is a directory.  If it is
# not then the original string is returned and the function value is false,
# otherwise returning true.

unified_path()
{
  local x=$1 y ret=1
  if [ ! "$x" ]; then
    # cd with no argument goes to $HOME.
    echo "$Cwd"
  else
    y=`expand_tilde "$x"`
    if [ -d "$y" ]; then
      (cd $y; set -P; pwd ; )
    else
      echo "$x"
      ret=0
    fi
  fi
  return $ret
}

# We call 'check_paths' for many reasons but only want to report if the
# check flag is set.

echo_check()
{
  if (( $check && ! $quiet )); then
    echo "$*"
  fi
}

# Check the path list for duplicate and nonexistant path segments.  If
# required it will report errors and or correct the path.  In any event the
# paths will be modified, if need be, for the correct type of reporting.

# NOTE:
# 1) Function reads and uses global '$paths' if 'check_paths -u' is used
#    $paths will be modified if global flags require it to be.

check_paths()
{
  local got='' gotc='' newp='' pat expat='' patc update=0
  local bad badp badp_ badpe badpe_ type='none' changed=0 tlpat=''
  if (( $# )) && [[ "Z$1" == 'Z-u' ]]; then
    update=1
  fi
  for pat in $paths ; do
    if (( $unified )); then
      case "$pat" in
      (/*) type='a' ;;
      (\~|\~/*) type='t' ;;
      (\~*) type='l' ; tlpat=${pat%%\/*} ;;
      (*) type='r' ;;
      esac
      pat=`unified_path $pat`
      changed=1
    fi
    if (( $abs )); then
      pat=`absolute_path $pat`
      changed=1
    elif (( $relati )); then
      pat=`relative_path -p "$pat"`
      changed=1
    elif (( $tilde )); then
      pat=`relative_path -p "$pat" '~'`
      changed=1
    elif (( $unified )); then
      case $type in
      (a) ;; # Already absolute.
      (r) pat=`relative_path -p "$pat"` ;;
      (t) pat=`relative_path "$pat" '~'` ;;
      (l) pat=`relative_path -p "$pat" "$tlpat"` ;;
      esac
    fi
    newp="$newp $pat"
    if [[ " $got" = *\ $pat\ * ]]; then
      echo_check "Repeat segment:  $pat"
      ret=1
    else
      got="$got $pat"
      bad=0
      expat=`expand_tilde $pat`
      if [ ! -d "$expat" ]; then
	echo_check "Invalid path:   $pat"
	badp="`bad_path $pat`"
        badpe=`expand_tilde $badp`
	if [ -L "$badpe" ]; then
	  echo_check "Broken link at: $badp"
	elif [ -e "$badpe" ]; then
	  echo_check "Path is a file: $badp"
        else
          badp_=`chop_seg $badp`
          badpe_=`expand_tilde $badp_`
          if [ ! -d "$badpe_" -a -e "$badpe_" ]; then
            echo_check "Path is a file: $badp_"
          elif [ -d "$badpe_" -a ! -x "$badpe_" ]; then
            echo_check "Non-executable: $badp_"
          else
            echo_check "Does not exist: $badp"
          fi
	fi
	bad=1
	ret=1
      elif [ ! -x "$expat" ]; then
	echo_check "Non-executable: $pat"
	bad=1
	ret=1
      fi
      if (( ! $bad )); then
	gotc="$gotc $pat"
      fi
    fi
  done
  if (( $correct && $update )); then
    paths=${gotc#\ }
  elif (( $changed )); then
    paths=${newp#\ }
  fi
  return 0
}

# A debugging function.  The function returns true.

show_info()
{
  local cnt i
  echo "Reserveds: $reserveds"
  echo "Builtins: $builtins"
  echo "Aliases: $aliases"
  echo "Functions: $functions"
  echo "Alias definitions:"
  cnt=0
  for i in $aliases ; do
    echo "  $i = ${aliases_[$cnt]}"
    cnt=$(($cnt+1))
  done
  cnt=0
  echo "Function definitions:"
  for i in $functions ; do
    echo "  $i()" >&2
    while : ; do
      i="${functions_[$cnt]}"
      echo "    $i"
      cnt=$(($cnt+1))
      if [ "$i" = "}" ]; then
        break
      fi
    done
  done
  return 0;
}

# Lookup a word in the database and display the result.
# Usage: lookup_data -a|-f word [prefix]
# Where a|f specifies if the word is an alias or a function.  The function
# returns true if item was found and false otherwise.

lookup_info()
{
  local db=$1 word=$2 cnt=0 i ret=1 pre=''
  if [ $# -ge 3 ]; then
    pre="$3"
  fi
  if [ "Z$db" = 'Z-a' ]; then
    for i in $aliases ; do
      if [ "$i" = "$word" ]; then
        echo "$pre${aliases_[$cnt]}"
        ret=0
        break
      fi
      cnt=$(($cnt+1))
    done
  else
    for i in $functions ; do
      if [ "$i" = "$word" ]; then
  	while : ; do
  	  i="${functions_[$cnt]}"
  	  echo "$pre$i"
  	  cnt=$(($cnt+1))
  	  if [ "$i" = "}" ]; then
            ret=0
  	    break
  	  fi
  	done
      else
  	while : ; do
  	  cnt=$(($cnt+1))
  	  if [ "${functions_[$cnt]}" = "}" ]; then
            cnt=$(($cnt+1))
  	    break
  	  fi
  	done
      fi
    done
  fi
  return $ret
}


# Check if a shell exists.  Used for -x option.  Return false if not found.

check_shell()
{
  if (( $testing )); then
    echo "check_shell($1)" >&2
  fi
  local ret=0 aha
  parse_ps "$1"
  aha=`$Shellps -c ' ' 2>&1 >/dev/null`
  if [ "$aha" ]; then
    echo "Cannot find shell: '$Shellps'.  Treating it as virtual."
    Shell_a=`last_seg $Shell_i`
    virtual=1
    ret=1
  fi
  return $ret
}


# Identify the shell by testing.  Tests with the comment 'Positive' may be
# considered as positive identification of the shell.

# NOTE:  Do not adjust formating of 'EOF' lines.

# id_shell 'pathname'

id_shell()
{
  if (( $testing )); then
    echo "id_shell($1)" >&2
  fi
  local shellp="$1" ret=0 shells=' bash sh ash tcsh csh ksh zsh '
  local sh_name version='' pathname='' uses_c='No' uses_s='No' x

  if (( $native )); then
    Shell_a='bash'
    return $ret
  fi
#  echo Analysing $shellp.

  x=`$shellp -c "echo Testing"`
  if [ "$x" = 'Testing' ]; then
    uses_c='Yes'
  fi

  x=`$shellp -s <<EOF
    echo Testing
EOF
`

  if [ "$x" = 'Testing' ]; then
    uses_s='Yes'
  fi

  if [[ "$shells" == *\ @(tcsh|csh)\ * ]] \
     && [[ "$shells" == *\ !(tcsh|csh)\ * ]]; then
#    echo Test 1
    x=`$shellp -f -s <<EOF 2>&1
      setenv PATH=''
      foreach i (Testing)
	 echo \\\$i
      end
EOF
`
#  echo "x <$x>"
    if [ "$x" == 'Testing' ]; then
      shells=${shells/\ bash\ /\ }
      shells=${shells/\ sh\ /\ }
      shells=${shells/\ ash\ /\ }
      shells=${shells/\ ksh\ /\ }
    else
      shells=${shells/\ tcsh\ /\ }
      shells=${shells/\ csh\ /\ }
    fi
  fi

  # Positive tcsh id.
  if [[ "$shells" == *\ tcsh\ * ]] \
     && [[ "$shells" == *\ csh\ * ]]; then
#    echo Test 2
    x=`$shellp -f -s <<EOF 2>&1
  #    setenv PATH=''
      ls-F
EOF
`
  #echo "x <$x>"
    if [[ "$x" = *ls-F:* ]]; then
      shells=${shells/\ tcsh\ /\ }
    else
      shells=' tcsh '
    fi
  fi

  # Positive bash id.
  if [[ "$shells" == *\ bash\ * ]] \
     && [[ "$shells" == *\ !(bash)\ * ]]; then
#    echo Test 3
    BASH_VERSION=''
    x=`$shellp -s <<EOF 2>&1
    echo \\\$BASH_VERSION
EOF
`
  #echo "x <$x>"
    if [ "$x" ]; then
      shells=" bash "
      version=$x
    else
      shells=${shells/\ bash\ /\ }
    fi
  fi

  if [[ "$shells" == *\ ksh\ * ]] \
     && [[ "$shells" == *\ !(ksh)\ * ]]; then
#    echo Test 4
    x=`$shellp -s << EOF 2>&1
      aa=''
      aa[2]=Testing
      echo \\\${aa[2]}
EOF
`
  #echo "x <$x>"
    if [ "$x" = 'Testing' ]; then
      shells=${shells/\ csh\ /\ }
      shells=${shells/\ tcsh\ /\ }
      shells=${shells/\ sh\ /\ }
      shells=${shells/\ ash\ /\ }
    else
      shells=${shells/\ ksh\ /\ }
    fi
  fi

  # Positive ash id.
  if [[ "$shells" == *\ ash\ * ]] \
     && [[ "$shells" == *\ !(ash)\ * ]]; then
#    echo Test 5
    x=`$shellp -f -s <<EOF 2>&1
      PATH=''
      echo Testing
      lc Repeat
      Repeat
EOF
`
#  echo "x <$x>"
    if [ "$x" == 'Testing' ]; then
      shells=' ash '
    else
      shells=${shells/\ ash\ /\ }
    fi
  fi

  # Positive zsh id.
  if [[ "$shells" == *\ zsh\ * ]] \
     && [[ "$shells" == *\ !(zsh)\ * ]]; then
#    echo Test 6
    x=`$shellp -s <<EOF 2>&1
      emulate -R zsh
EOF
`
#  echo "x <$x>"
    if [ "$x" == '' ]; then
      shells=' zsh '
    else
      shells=${shells/\ zsh\ /\ }
    fi
  fi

  shells=${shells#\ }
  shells=${shells%\ }
  if [[ "$shells" == *\ * ]] || [ ! "$shells" ]; then
    error_all=2
    error_out "OOPS: Failed to identify shell '$shellp'.  Think it is one of:"
    error_out "  <$shells>"
    error_out "  Trying to continue by assuming it is '$Shell_i'."
    Shell_a=$Shell_i
  else
    Shell_a="$shells"
    if (( $dump && $shellf )); then
      sh_name=`find $shells`
      case $shells in
      (bash)
	pathname=`$sh_name -f -c "echo \\\$BASH"`
	version=`$sh_name -f -c "echo \\\$BASH_VERSION"`
	;;
      (tcsh)
	# $shell is set by tcsh.
	pathname=`$sh_name -f -c "echo \\\$shell"`
	version=`$sh_name -f -c "echo \\\$tcsh"`
        ;;
      (csh)
        pathname=`$sh_name -f -c "echo \\\$shell"`
        ;;
      esac

      echo "'$shellp' is a '${shells}' shell.  $version"
      if [ "$pathname" ]; then
	echo "Actual path: $pathname"
      fi
      echo "Uses -c $uses_c"
      echo "Uses -s $uses_s"
    fi
  fi
  return $ret
}

# Look up PPID and ps_string.

get_psdata()
{
  local Sl1='' ret=0 ps_line ps_str ps_old=''
  if (( $testing )); then
    echo "get_shell()" >&2
  fi
  ps_line=`$PS | $GREP "^ *[^ ][^ ]*  *[^ ][^ ]*  *$PPID"`
  if [ ! "$ps_line" ]; then
    error_all=2
    error_out "WARNING: Could not find parent process."
    # Fake a ps output.
    ps_line="x1 x1 x1 NoPID NoPPID :0 rsh?"
  fi
  # Get line into form delimited by single spaces.
  ps_line=${ps_line##*([[:space:]])+([[:digit:]])+([[:space:]])\
+([[:digit:]])+([[:space:]])+([[:digit:]])+([[:space:]])}
  ps_line=${ps_line%%+([[:space:]])}
  ps_str=${ps_line#*:+([[:digit:]])+([[:space:]])}
  while [[ "$ps_str" != "$ps_old" ]]; do
    ps_old="$ps_str"
    ps_str=${ps_str%%+([[:space:]])[^-]*([^[:space:]])}
  done
  pppid=${ps_line%%\ *}
  parse_ps "$ps_str"
}

# Determine shell type (and how IT was invoked) that invoked this script.
# Do this by parsing a "command line".  Possible call sequence is:
# $WHICH_ENV then ps, just ps or just -x argument.

parse_ps()
{
  if (( $testing )); then
    echo "parse_ps($1)" >&2
  fi
  local ps_str="$1" shell_i_t shellps_t shellm_t shellp_t tmp
  # Now ps_str consists of '[-][path]<shell> [flags] [arguments]'.  The shell
  # is a login shell if it starts with '-' or has certain, shell dependent,
  # flags.  For the moment we content ourselves by extracting the various
  # elements.  If $Shell_i is already defined then it is taken as a
  # correction for <shell>.

  ps_str=${ps_str#+([[:space:]])}
  ps_str=${ps_str%+([[:space:]])}
  shellps_t=${ps_str%%[[:space:]]*}
  if [[ "$ps_str" == *[[:space:]]* ]]; then
    shellfa_t=${ps_str#+([^[:space:]])[[:space:]]}
    shellfa_t=${shellfa_t#+([[:space:]])}
  else
    shellfa_t=''
  fi
  if [[ "$shellps_t" == -* ]]; then
    shellm_t='-'
    shellps_t=${shellps_t#-}
  else
    shellm_t=''
  fi
  shellp_t=`chop_seg $shellps_t`
  shell_i_t=`last_seg $shellps_t`

  if [ "$shellp_t" ]; then
    Sl1='/'
  else
    Sl1=''
  fi
  if [ "Z$shellfa_t" != "Z" ]; then # Variable 'Shellfa' is typically '-xx'.
    spc=' '
  else
    spc=''
  fi
  if [ "$ps_str" != "$shellm_t$shellp_t$Sl1$shell_i_t$spc$shellfa_t" ]; then
    error_all=2
    error_out "OOPS: A parse mismatch."
    error_out "ps says <$ps_str>"
    error_out "<[$shellm_t][$shellp_t]$Sl1<$shell_i_t>$spc[$shellfa_t]>"
  fi

  if (( $got_shell )); then
    tmp="$shellm_t$shellps_t $shellfa_t"
    tmp=${tmp/#?/-}
    if [[ "$Shellm$Shellps $Shellfa" != "$shellm_t$shellps_t $shellfa_t"
       && "$Shellm$Shellps $Shellfa" != "$tmp" ]]; then
      error_all=2
      error_out "WARNING: Replacing: '$Shellm$Shellps $Shellfa'"
      error_out "              With: '$shellm_t$shellps_t $shellfa_t'"
    elif (( $dump )); then
      echo "Replacing: '$Shellm$Shellps $Shellfa'"
      echo "     With: '$shellm_t$shellps_t $shellfa_t'"
    fi
  fi
  Shellps="$shellps_t"
  Shellm="$shellm_t"
  Shellp="$shellp_t"
  Shell_i="$shell_i_t"
  Shellfa="$shellfa_t"

  if [ ! "$Shell_i" ]; then
    error_all=2
    error_out "ERROR: Shell name cannot be the null string."
    error_out "       Assuming it is bash and continuing."
    Shell_i='bash'
    if [ "$Shellp" ]; then
      Shellps="$Shellp/$Shell_i"
    else
      Shellps="$Shell_i"
    fi
  fi

  got_shell=1
}

# Read information from stdin.  Actually it parses the first line and then
# uses a shell specific function to parse rest of stdin.  Function returns
# true if no errors or false otherwise.

get_stdin_info()
{
  local line ret=1
  if (( $testing )); then
    echo "get_stdin_info()" >&2
  fi
  read line
  if get_env_info "$line" ; then
    if [ ! "$Shell_a" ]; then
      id_shell "$Shellps"
    fi
    if (( $Need_none )); then
      return $ret
    fi
    case $Shell_a in
    (bash) get_bash_info ;;
    (sh) get_sh_info ;;
    (tcsh) get_tcsh_info ;;
    (csh) get_csh_info ;;
    (ksh) get_ksh_info ;;
    (ash) get_ash_info ;;
    (zsh) get_zsh_info ;;
    (*)
      if [ "$Shell_a" ]; then
        if (( ! $native )); then
          error_all=2
	  error_out "SORRY: The '$Shell_a' parser is not implemented yet."
        fi
	ret=1
      fi
      ;;
    esac
    ret=0
  fi
  if [ "$aliases" ]; then
    Need_al=0
  fi
  if [ "$functions" ]; then
    Need_fu=0
  fi
  if [ "$builtins" ]; then
    Need_bu=0
  fi
  if [ "$reserveds" ]; then
    Need_re=0
  fi
  return $ret
}

# These are shell specific functions for get_stdin_info() and
# get_file_info().  They take input from stdin, parse it and put it into
# arrays and variables.  On startup these variables are cleared, we just
# add to them here.  The function returns true.
#
# Use this alias in bash to use this.
# alias which='(echo $$ ; alias ; enable ; declare -f ; ) | which -g'

get_bash_info()
{
  if (( $testing )); then
    echo "get_bash_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (alias\ *)
      if (( $Need_al )); then
        temp="${temp#alias\ }"
        aliases="$aliases ${temp%%=*}"
        aliases_[${#aliases_[*]}]=${temp#*=}
      fi
      ;;
    (declare\ -f\ *)
      if (( $Need_al )); then
	temp="${temp#declare\ -f\ }"
	functions="$functions ${temp%\ *}"
	while read temp ; do
	  functions_[${#functions_[*]}]="$temp"
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      else
	while read temp ; do
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      fi
      ;;
    (enable\ *)
      if (( $Need_bu )); then
        builtins="$builtins ${temp#enable\ }"
      fi
      ;;
    esac
  done
  return 0
}

get_sh_info()
{
  if (( $testing )); then
    echo "get_sh_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (*=\'*)
      if (( $Need_al )); then
	aliases="$aliases ${temp%%=*}"
	aliases_[${#aliases_[*]}]==${temp#*=}
      fi
      ;;
    (*\(\)\{)
      if (( $Need_fu )); then
	functions="$functions ${temp%\(\)\{}"
	functions_[${#functions_[*]}]="{"
	while read temp ; do
	  functions_[${#functions_[*]}]="$temp"
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      else
	while read temp ; do
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      fi
      ;;
    esac
  done
  return 0
}

get_tcsh_info()
{
  if (( $testing )); then
    echo "get_tcsh_info()" >&2
  fi
  local temp name
  while read -r temp ; do # -r is a bodge!????
    case "$temp" in
    (*[[:space:]]*) # Aliases.
      if (( $Need_al )); then
        aliases="$aliases ${temp%%[[:space:]]*}"
        temp=${temp#*[[:space:]]}
        temp="${temp##+([[:space:]])}"
	temp=${temp//\'/"'\''"}
        aliases_[${#aliases_[*]}]="'$temp'"
      fi
      ;;
    (*)
      if (( $Need_bu )) && [ "$temp" ]; then
        builtins="$builtins $temp"
      fi
      ;;
    esac
  done
  return 0
}

get_csh_info()
{
  if (( $testing )); then
    echo "get_csh_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (*[[:space:]]*) # Aliases.
      if (( $Need_al )); then
        aliases="$aliases ${temp%%\ *}"
        temp=${temp#*[[:space:]]}
        temp="${temp##+([[:space:]])}"
	temp=${temp//\'/"'\''"}
        aliases_[${#aliases_[*]}]="'$temp'"
      fi
      ;;
    esac
  done
  return 0
}

get_ksh_info()
{
  if (( $testing )); then
    echo "get_ksh_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (*=\'*)
      if (( $Need_al )); then
        aliases="$aliases ${temp%%=*}"
        aliases_[${#aliases_[*]}]="${temp#*=}"
      fi
      ;;
    (function\ *)
      if (( $Need_fu )); then
        functions="$functions ${temp%function\ }"
        while read temp ; do
          functions_[${#functions_[*]}]="$temp"
          if [ "$temp" = "}" ]; then
            break
          fi
        done
      else
        while read temp ; do
          if [ "$temp" = "}" ]; then
            break
          fi
        done
      fi
      ;;
    esac
  done
  return 0
}

get_ash_info()
{
  if (( $testing )); then
    echo "get_ash_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (*=\'*)
      if (( $Need_al )); then
	aliases="$aliases ${temp%%=*}"
        aliases_[${#aliases_[*]}]="${temp#*=}"
      fi
      ;;
    (*\(\)\{)
      if (( $Need_fu )); then
	functions="$functions ${temp%\(\)\{}"
	functions_[${#functions_[*]}]="{"
	while read temp ; do
	  functions_[${#functions_[*]}]="$temp"
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      else
	while read temp ; do
	  if [ "$temp" = "}" ]; then
	    break
	  fi
	done
      fi
      ;;
    esac
  done
  return 0
}

get_zsh_info()
{
  if (( $testing )); then
    echo "get_zsh_info()" >&2
  fi
  local temp name
  while read temp ; do
    case "$temp" in
    (*=*)
      if (( $Need_al )); then
        aliases="$aliases ${temp%%=*}"
        aliases_[${#aliases_[*]}]="${temp#*=}"
      fi
      ;;
    (*)
      if (( $Need_bu )); then
        builtins="$builtins $temp"
      fi
      ;;
    esac
  done
  return 0
}

get_file_info()
{
  local i temp got='' keep='' ret=0
  if (( $testing )); then
    echo "get_file_info()" >&2
  fi
  # Need to trace File_env and if it grew during the read then redo for
  # the extra files.
  while [ "$File_env" != "$keep" ]; do
    File_env=${File_env//:/\ }
    keep=$File_env
    for i in $File_env
    do
      if [[ " $got " != *\ $i\ * ]]; then
        got="$got $i"
        i=`expand_tilde $i`
        if [ -f "$i" ]; then
	  get_stdin_info < $i
        else
	  error_all=2
	  error_out "WARNING: File '$i' is not readable."
	  ret=1
        fi
      fi
    done
  done
  return $ret
}


# Read the environmental variable (or first line) and set any global values
# from it.

get_env_info()
{
  local str=$1 i st=0 var val del='' ok=0
  if (( $testing )); then
    echo "get_env_info($1)" >&2
  fi
  for i in $str ; do
    case $st in
    (0)
      if [ "$i" == "$pppid" ]; then
        # Accept parent of $$ to allow for `which ...` use.
        st=1
        subshell=1
      elif [ "$i" == "$ppid" \
            -o \( $Shell_name -eq 1 -a "$i" == "$Shell_i" \) ]; then
        st=1
      elif (( $Shell_name )); then
        error_all=2
     error_out "WARNING: WHICH_ENV or line 1 does not quote PID or shell name."
     error_out "         Shell ($Shell_i), PID ($ppid) != '$i'.  Ignoring it."
	return 1
      else
        error_all=2
	error_out "WARNING: WHICH_ENV or Line 1 does not quote PID."
	error_out "         Shell PID ($ppid) != '$i'.  Ignoring it."
	return 1
      fi
      ;;
    (1)
      case $i in
      (@(shell|shella|file|oldpwd|source|esource|eval)=*)
        var=${i%%=*}
        val=${i#*=}
        st=1
        case $val in
        (\'*) del="'" ;;
        (\"*) del='"' ;;
        (*) st=1; ok=1 ;;
        esac
        if [ $del ]; then
          val=${val#$del}
          st=2
          case $val in
          (*\\$del) ;;
          (*$del) val=${val%$del}; st=1; ok=1  ;;
          (*) ;;
          esac
        fi
        ;;
      (*)
        error_all=2
        error_out "OOPS: Bad Parse '$i' in get_env_info()."
	;;
      esac
      ;;
    (2)
      case $i in
      (*\\$del) ;;
      (*$del) i=${i%$del}; st=1; ok=1 ;;
      (*) ;;
      esac
      val="$val $i"
      ;;
    (*)
      error_all=2
      error_out "OOPS: Bad state ($st) in get_env_info()."
      ;;
    esac
    if (( $ok )); then
#      echo ">>" $var = $val
      if [ "$del" = '"' ]; then
	eval val="$del$val$del"
#        echo ">>" $var = $val
        del=''
      fi
      ok=0
      case $var in
      (shell)
        # Let parse_ps generate the warning.
	parse_ps "$val"
        ;;
      (shella)
        if [ "$Shell_a" -a "$val" != "$Shell_a" ]; then
          error_all=2
     error_out "WARNING: Reseting shell actual name from '$Shell_a' to '$val'."
        fi
        Shell_a=$val
        ;;
      (file) File_env="$File_env $val" ;;
      (oldpwd) OLDPWD=$val ;;
      (source) Startsrc="$val" ;;
      (esource) Stopsrc="$val" ;;
      (eval) eval $val ;;
      esac
    fi
  done
  return 0
}

# Print shell name and how it was invoked.  Function returns true.

report_shell()
{
  local fa=''
  if (( $testing )); then
    echo "report_shell()" >&2
  fi
  if (( $dump && ! $exec || $shellf )); then
    if (( $login )); then
      textl='login '
    else
      textl=''
    fi
    if [ "Z$Shellfa" != "Z" ]; then # Variable 'Shellfa' is typically '-xx'.
      spc=' '
    else
      spc=''
    fi
    if (( $shell_x )); then
      fa='fake '
    fi
    if (( $shellf )); then
      if (( $terse )); then
	echo "Shell -> $Shellm$Shellp$Shell_i$spc$Shellfa"
      else
	echo "Started from a $fa'$Shell_i' ${textl}shell: '$Shellm$Shell_i$spc$Shellfa' (PID = $ppid)."
        if (( $subshell )); then
          echo "Which was apparently invoked in a subshell, its PPID = $pppid."
        fi
        if (( $native )); then
          echo "  Native mode: Using a '$Shell_a' shell."
        elif [[ "$Shell_i" != "$Shell_a" ]]; then
          echo "  Shell is actually a '$Shell_a' shell."
        fi
      fi
    fi
  fi
  return 0
}

# Synthesise a shell and use it to determine functions, aliases and
# builtins.  This function uses shell specific functions.  It returns true
# if a function was called or false otherwise.

get_synth_info()
{
  # Set login indicator, subject to flags.
  if (( $testing )); then
    echo "get_synth_info()" >&2
  fi
  local ret=0 reserve='' built=''
  if [ "Z$Shellm" = 'Z-' ]; then
    login=1
  else
    login=0
  fi
  if (( $native )); then
    # No alias and do not want functions.
    get_bash_info <<EOF
`enable`
EOF
    return $ret
  fi
  # Switch by actual shell and do any 'Shell_i' processing there.
  case $Shell_a in
  (bash) bash_synth_info ;;
  (sh) sh_synth_info ;;
  (tcsh) tcsh_synth_info ;;
  (csh) csh_synth_info ;;
  (ksh) ksh_synth_info ;;
  (ash) ash_synth_info ;;
  (zsh) zsh_synth_info ;;
  (*)
    error_all=2
    error_out "SORRY: Unknown shell ($Shell_a)."
    ret=1
    ;;
  esac
  if [ "$aliases" ]; then
    Need_al=0
  fi
  if [ "$functions" ]; then
    Need_fu=0
  fi
  if [ "$builtins" ]; then
    Need_bu=0
  fi
  if [ "$reserveds" ]; then
    Need_re=0
  fi
  return $ret
}

bash_synth_info()
{
  if (( $testing )); then
    echo "bash_synth_info()" >&2
  fi
  local ret=0
  # Check the startup flags and set some variables.

  case "$Shellfa" in
  (*-login*) login=1 ;;
  esac

  posix=0
  case "$Shellfa" in
  (*-posix*) posix=1 ;;
  esac
  case "$Shellfa " in
  (*-p\ *) posix=0 ;; # Privileged mode.
  esac

  norc=0
  case "$Shellfa" in
  (*-norc*) norc=1 ;;
  esac

  noprofile=0
  case "$Shellfa" in
  (*-noprofile*) noprofile=1 ;;
  esac

  rcfile=""
  case "$Shellfa" in
  (*-rcfile*)
    rcfile=${Shellfa##*-rcfile\ }
    rcfile=${rcfile%%\ *}
    ;;
  esac

  if (( ! $Givenrcs )); then

    # Determine which files should be sourced.  (With comments from the
    # (bash) manual.)

    # When bash is started in posix mode, as with the -posix command
    # line option, it follows the Posix standard for startup files.  In
    # this mode, the ENV variable is expanded and that file sourced; no
    # other startup files are read.

    # On startup:
    #     if the environment variable ENV is non-null, expand
    #     it and source the file it names, as if the command
    #         if [ "$ENV" ]; then . $ENV; fi
    #     had been executed, but do not use PATH to search
    #     for the pathname.  When not started in Posix mode, bash
    #     looks for BASH_ENV before ENV.

    if [ ! "$Startsrc" ]; then
      if [ "$ENV" ]; then
	if [ -r "$ENV" -a -f "$ENV" ]; then
	  Startsrc=". $ENV > /dev/null 2>&1 ;"
	fi
      elif (( $login )); then
	# Manual says:
	# On login (subject to the -noprofile option):
	#        if /etc/profile exists, source it.
	#
	#        if ~/.bash_profile exists, source it,
	#          else if ~/.bash_login exists, source it,
	#            else if ~/.profile exists, source it.
	if (( $noprofile )); then
	  : # No action.
	elif [ -r /etc/profile -a -f /etc/profile ]; then
	  Startsrc="$Startsrc . /etc/profile > /dev/null 2>&1 ;"
	fi

	if (( $noprofile )); then
	  : # No action.
	elif [ -r ~/.bash_profile -a -f ~/.bash_profile ]; then
	  Startsrc="$Startsrc . ~/.bash_profile > /dev/null 2>&1 ;"
	elif [ -r ~/.bash_login -a -f ~/.bash_login ]; then
	  Startsrc="$Startsrc . ~/.bash_login > /dev/null 2>&1 ;"
	elif [ -r ~/.profile -a -f ~/.profile ]; then
	  Startsrc="$Startsrc . ~/.profile > /dev/null 2>&1 ;"
	fi

      else
	# _Assuming_ it is interactive.
	# Manual says:
	# Non-login interactive shells:
	# On startup (subject to the -norc and -rcfile options):
	#      if ~/.bashrc exists, source it.
	if (( $norc )); then
	  : # No action.
	elif [ "$rcfile" ]; then
	  if [ -r ~/$rcfile -a -f ~/$rcfile ]; then
	    Startsrc="$Startsrc . ~/$rcfile > /dev/null 2>&1 ;"
	  fi
	elif [ -r ~/.bashrc -a -f ~/.bashrc ]; then
	  Startsrc="$Startsrc . ~/.bashrc > /dev/null 2>&1 ;"
	fi
      fi
    fi

    if [ ! "Stopsrc" ]; then
      if [ "$ENV" ]; then
	:
      elif (( $login )); then
	# Manual says:
	# On exit:
	#        if ~/.bash_logout exists, source it.
	if (( $noprofile )); then
	  : # No action.
	elif [ -r ~/.bashrc_logout -a -f ~/.bashrc_logout ]; then
	  Stopsrc="$Stopsrc . ~/.bashrc_logout > /dev/null 2>&1 ;"
	fi
      fi
    fi
  fi
  get_bash_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  declare -f
  enable
  $Stopsrc
# Do not adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

sh_synth_info()
{
  if (( $testing )); then
    echo "sh_synth_info()" >&2
  fi
  local ret=0
  # Check the startup flags and set some variables.

  case "$Shellfa" in
  (*-login*) login=1 ;;
  esac

  norc=0
  case "$Shellfa" in
  (*-norc*) norc=1 ;;
  esac

  noprofile=0
  case "$Shellfa" in
  (*-noprofile*) noprofile=1 ;;
  esac

  rcfile=""
  case "$Shellfa" in
  (*-rcfile*)
    rcfile=${Shellfa##*-rcfile\ }
    rcfile=${rcfile%%\ *}
    ;;
  esac

  if (( ! $Givenrcs )); then

    # Determine which files should be sourced.  (With comments from the
    # manual.)

    # If Bash is invoked as sh, it tries to mimic the behavior of sh as
    # closely as possible.  For a login shell, it attempts to source
    # only /etc/profile and ~/.profile, in that order.  The -noprofile
    # option may still be used to disable this behavior.  A shell
    # invoked as sh does not attempt to source any other startup files.

    if [ ! "$Startsrc" ]; then
      if (( $login && ! $noprofile )); then
	if [ -r /etc/profile -a -f /etc/profile ]; then
	  Startsrc="$Startsrc . /etc/profile > /dev/null 2>&1 ;"
	fi
	if [ -r ~/.profile -a -f ~/.profile ]; then
	  Startsrc="$Startsrc . ~/.profile > /dev/null 2>&1 ;"
	fi
      fi
    fi
  fi

  get_sh_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  set
# Do not adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

tcsh_synth_info()
{
  if (( $testing )); then
    echo "tcsh_synth_info()" >&2
  fi
  local ret=0
  if [ "Z$Shellfa" == 'Z-l' ]; then
    # The '-l' must be alone.
    login=1
  fi

  minus_f=0
  case "$Shellfa " in
  (*-f\ *) minus_f=1 ;;
  esac

  if (( ! $Givenrcs )); then

    # Determine which files should be sourced.  (With comments from the
    # manual.)

    # Startup and shutdown

    # A login shell begins by executing commands from the system files
    # /etc/csh.cshrc and /etc/csh.login.  It then executes commands
    # from files in the user's home directory: first ~/.tcshrc (+) or,
    # if ~/.tcshrc is not found, ~/.cshrc, then ~/.history (or the
    # value of the histfile shell variable), then ~/.login, and finally
    # ~/.cshdirs (or the value of the dirsfile shell variable) (+).
    # The shell may read /etc/csh.login before instead of after
    # /etc/csh.cshrc, and ~/.login before instead of after ~/.tcshrc or
    # ~/.cshrc and ~/.history, if so compiled; see the version shell
    # variable. (+)

    # Non-login shells read only /etc/csh.cshrc and ~/.tcshrc or
    # ~/.cshrc on startup.

    # When a login shell terminates it sets the logout shell variable
    # to `normal' or `auto- matic' as appropriate, then executes
    # commands from the files /etc/csh.logout and ~/.logout. The shell
    # may drop DTR on logout if so compiled; see the ver- sion shell
    # variable.

    # The names of the system login and logout files vary from system
    # to system for compatibility with different csh(1) variants; see
    # FILES.

    # -f  The shell ignores ~/.tcshrc, and thus starts faster.

    if [ ! "$Startsrc" ]; then
      if (( $login )); then
	if [ -r /etc/csh.cshrc -a -f /etc/csh.cshrc ]; then
	  Startsrc="$Startsrc source /etc/csh.cshrc >& /dev/null ;"
	fi
	if [ -r /etc/csh.login -a -f /etc/csh.login ]; then
	  Startsrc="$Startsrc source /etc/csh.login >& /dev/null ;"
	fi

	if (( $minus_f )); then
	  : # No action.
	elif [ "$Shell_a" = "tcsh" -a -r ~/.tcshrc -a -f ~/.tcshrc ]; then
	  Startsrc="$Startsrc source ~/.tcshrc >& /dev/null ;"
	elif [ -r ~/.cshrc -a -f ~/.cshrc ]; then
	  Startsrc="$Startsrc source ~/.cshrc >& /dev/null ;"
	fi

	if [ -r ~/.login -a -f ~/.login ]; then
	  Startsrc="$Startsrc source ~/.login >& /dev/null ;"
	fi

      else
	if [ -r /etc/csh.cshrc -a -f /etc/csh.cshrc ]; then
	  Startsrc="$Startsrc source /etc/csh.cshrc >& /dev/null ;"
	fi

	if (( $minus_f )); then
	  : # No action.
	elif [ "$Shell_a" = "tcsh" -a -r ~/.tcshrc -a -f ~/.tcshrc ]; then
	  Startsrc="$Startsrc source ~/.tcshrc >& /dev/null ;"
	elif [ -r ~/.cshrc -a -f ~/.cshrc ]; then
	  Startsrc="$Startsrc source ~/.cshrc >& /dev/null ;"
	fi
      fi
    fi

    if [ ! "Stopsrc" ]; then
      if (( $login )); then
	# Logout.
	if [ -r /etc/csh.logout -a -f /etc/csh.logout ]; then
	  Stopsrc="$Stopsrc source /etc/csh.logout >& /dev/null ;"
	fi
	if [ -r ~/.logout -a -f ~/.logout ]; then
	  Stopsrc="$Stopsrc source ~/.logout >& /dev/null ;"
	fi
      fi
    fi
  fi

  get_tcsh_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  builtins
  $Stopsrc
# DO NOT adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

csh_synth_info()
{
  if (( $testing )); then
    echo "csh_synth_info()" >&2
  fi
  local ret=0
  if [ "Z$Shellfa" = 'Z-l' ]; then
    # The '-l' must be alone.
    login=1
  fi

  minus_f=0
  case "$Shellfa " in
  (*-f\ *) minus_f=1 ;;
  esac

  if (( ! $Givenrcs )); then

    # Determine which files should be sourced.  See above but adjusted for
    # csh.

    if [ ! "$Startsrc" ]; then
      if (( $login )); then
	if [ -r /etc/csh.cshrc -a -f /etc/csh.cshrc ]; then
	  Startsrc="$Startsrc source /etc/csh.cshrc >& /dev/null ;"
	fi
	if [ -r /etc/csh.login -a -f /etc/csh.login ]; then
	  Startsrc="$Startsrc source /etc/csh.login >& /dev/null ;"
	fi

	if (( $minus_f )); then
	  : # No action.
	elif [ -r ~/.cshrc -a -f ~/.cshrc ]; then
	  Startsrc="$Startsrc source ~/.cshrc >& /dev/null ;"
	fi

	if [ -r ~/.login -a -f ~/.login ]; then
	  Startsrc="$Startsrc source ~/.login >& /dev/null ;"
	fi

      else
	if [ -r /etc/csh.cshrc -a -f /etc/csh.cshrc ]; then
	  Startsrc="$Startsrc source /etc/csh.cshrc >& /dev/null ;"
	fi

	if (( $minus_f )); then
	  : # No action.
	elif [ -r ~/.cshrc -a -f ~/.cshrc ]; then
	  Startsrc="$Startsrc source ~/.cshrc >& /dev/null ;"
	fi
      fi
    fi
    if [ ! "$Startsrc" ]; then
      if (( $login )); then
	# Logout.
	if [ -r /etc/csh.logout -a -f /etc/csh.logout ]; then
	  Stopsrc="$Stopsrc source /etc/csh.logout >& /dev/null ;"
	fi
	if [ -r ~/.logout -a -f ~/.logout ]; then
	  Stopsrc="$Stopsrc source ~/.logout >& /dev/null ;"
	fi
      fi
    fi
  fi

  get_csh_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  $Stopsrc
# DO NOT adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

ksh_synth_info()
{
  if (( $testing )); then
    echo "ksh_synth_info()" >&2
  fi
  local ret=0

  # Put your code here.

  get_ksh_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  typeset -f
  $Stopsrc
# DO NOT adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

ash_synth_info()
{
  if (( $testing )); then
    echo "ash_synth_info()" >&2
  fi
  local ret=0

  # Put your code here.???
  # Can anything be sensibly done here?

  get_ash_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  # Put your code here.???
  # Cannot find a way to list anything useful.
  $Stopsrc
# DO NOT adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

zsh_synth_info()
{
  if (( $testing )); then
    echo "zsh_synth_info()" >&2
  fi
  local ret=0

  # Check the startup flags and set some variables.

  case "$Shellfa" in
  (*-l*) login=1 ;;
  esac

  case "$Shellfa" in
  (*-i*) interactive=1 ;;
  esac

  if (( ! $Givenrcs )); then

    # Determine which files should be sourced.  (With comments from the
    # (bash) manual.)

    # From the zsh version 3.0, June 26, 1996 manual.

    # Commands are first read  from  /etc/zshenv.   If  the  RCS
    # option  is unset within /etc/zshenv, all other initializa-
    # tion files are skipped.  Otherwise, commands are read from
    # $ZDOTDIR/.zshenv.   (If  ZDOTDIR  is  unset,  HOME is used
    # instead).  If the first character of argument zero  passed
    # to  the shell is -, or if the -l flag is present, then the
    # shell is assumed to be a login  shell,  and  commands  are
    # read   from  /etc/zprofile  and  then  $ZDOTDIR/.zprofile.
    # Then, if the shell is interactive, commands are read  from
    # /etc/zshrc  and  then  $ZDOTDIR/.zshrc.   Finally,  if the
    # shell is a login shell, /etc/zlogin  and  $ZDOTDIR/.zlogin
    # are read.

    if [ ! "$Startsrc" ]; then
      if [ -r /etc/zshenv -a -f /etc/zshenv ]; then
        Startsrc="$Startsrc . /etc/zshenv > /dev/null 2>&1 ;"
      fi
      if (( ! ${RCS:-0} )); then
	LOC=${ZDOTDIR:-$HOME}
	if [ "$LOC" != '' ] && [ -r $LOC/.zshenv -a -f $LOC/.zshenv ]; then
	  Startsrc="$Startsrc . $LOC/.zshenv > /dev/null 2>&1 ;"
	fi
        if (( $login )); then
	  if [ -r /etc/zprofile -a -f /etc/zprofile ]; then
	    Startsrc="$Startsrc . /etc/zprofile > /dev/null 2>&1 ;"
	  fi
	  if [ "$LOC" != '' ] && [ -r $LOC/.zprofile -a -f $LOC/.zprofile ]; then
	    Startsrc="$Startsrc . $LOC/.zprofile > /dev/null 2>&1 ;"
	  fi
        fi

	if (( $interactive )); then
	  if [ -r /etc/zshrc -a -f /etc/zshrc ]; then
	    Startsrc="$Startsrc . /etc/zshrc > /dev/null 2>&1 ;"
	  fi
	  if [ "$LOC" != '' ] && [ -r $LOC/.zshrc -a -f $LOC/.zshrc ]; then
	    Startsrc="$Startsrc . $LOC/.zshrc > /dev/null 2>&1 ;"
	  fi
	fi

        if (( $login )); then
	  if [ -r /etc/zlogin -a -f /etc/zlogin ]; then
	    Startsrc="$Startsrc . /etc/zlogin > /dev/null 2>&1 ;"
	  fi
	  if [ "$LOC" != '' ] && [ -r $LOC/.zlogin -a -f $LOC/.zlogin ]; then
	    Startsrc="$Startsrc . $LOC/.zlogin > /dev/null 2>&1 ;"
	  fi
        fi

	if [ ! "Stopsrc" ]; then
	  if (( $login )); then
	    # Manual implies, on exit:
	    if [ -r /etc/zlogout -a -f /etc/zlogout ]; then
	      Stopsrc="$Stopsrc . /etc/zlogout > /dev/null 2>&1 ;"
	    fi
	    if [ "$LOC" != '' ] && [ -r $LOC/.zlogout -a -f $LOC/.zlogout ]; then
	      Stopsrc="$Stopsrc . $LOC/.zlogout > /dev/null 2>&1 ;"
	    fi
	  fi
	fi
      fi
    fi
  fi

  get_zsh_info << EOF2
  `$Shellps -s << EOF1 2> /dev/null
  $Startsrc
  alias
  enable
  $Stopsrc
# DO NOT adjust the following three lines.
EOF1
  `
EOF2

  return $ret
}

# Set defaults for reserveds and builtins if not already set.  Note that
# this function does not use the declared shell even though it could (in
# some cases) so as to allow -x to investigate something not on the local
# system.  Function returns true if it set something or false otherwise.

get_default_info()
{
  if (( $testing )); then
    echo "get_default_info()" >&2
  fi
  local ret=1 reserve='' built=''
  case $Shell_a in
  (bash) reserve=$bash_reserved; built=$bash_builtin ;;
  (sh) reserve=$sh_reserved; built=$sh_builtin ;;
  (tcsh) reserve=$tcsh_reserved; built=$tcsh_builtin ;;
  (csh) reserve=$csh_reserved; built=$csh_builtin ;;
  (ksh) reserve=$ksh_reserved; built=$ksh_builtin ;;
  (ash) reserve=$ash_reserved; built=$ash_builtin ;;
  (zsh) reserve=$zsh_reserved; built=$zsh_builtin ;;
  (*) ret=1 ;; # Including native mode.
  esac
  if (( $Need_re )); then
    reserveds=$reserve
    Need_re=0
    ret=0
  fi
  if (( $Need_bu )); then
    builtins=$built
    Need_bu=0
    ret=0
  fi
  return $ret
}


# This function is called each time a file (not a link) is reported.  It
# maintains a database of inode numbers and reports any duplicates.  It
# returns true if a matching inode was found.

# This uses array lsi[] and globals $inodes and $indent.

inode_check()
{
  if (( $quiet )); then
    return $ret
  fi
  local offset item itemo ipath ino equiv i cnt ret=0 iteme
  offset="$1$indent"
  itemo="$2"
  item=`expand_tilde "$2"`
  ipath=`$LS -di $item`
  ipath=${ipath##+([[:space:]])}
  ino=${ipath%%[[:space:]]*}
  ipath="$ino $itemo"
  if [[ "$inodes" == *\ $ino\ * ]]; then
    # Already reported this inode.
    let cnt=0
    while (( $cnt <= ${#lsi[*]} )); do
      if [[ "${lsi[$cnt]}" == $ino\ * ]]; then
        iteme=${lsi[$cnt]##*[[:space:]]}
        if [[ "$itemo" == "$iteme" ]]; then
          echo "$dm${offset}s-> Multiple report."
        else
	  echo "$dm${offset}s-> $iteme"
        fi
	break
      fi
      let cnt+=1
    done
  else
    ret=1
    lsi[${#lsi[*]}]=$ipath
    inodes="$inodes$ino "
  fi
  return $ret
}


# The trace functions are used to report aliases, reserved words,
# functions, builtins and files in that order.  Functions returns true if
# a match is found or false otherwise.

# trace_?() offset ref file

# Offset is the indent given as a string of spaces, after each match in a
# chain this offset should be increased by $indent.

# Ref is the pointer string showing the type of reference (x->, a-> etc).

# File is the thing searched for.

# Trace_F() optionally follows links and reports what the file is, it uses
# mode dependent search.  Function returns true if the object was found and
# false otherwise.

trace_F()
{
  local pathl pathlo offset ref pathlw='' nexe ll hcnt hist ret=0 fnd=0
  local jump exists=0 report=0 bad='' there=1 link=0
  offset="$1"
  ref="$2"
  pathlo="$3" # Keep original path format for first report.
  pathl=`expand_tilde "$3"`

  if [ -L "$pathl" ]; then
    link=1
  fi

  if [ ! -e "$pathl" ]; then
    if (( $link && ! $links )); then
      bad="$bad, Broken link"
    else
      bad="$bad, Does not exist"
    fi
    there=0
  else
    if [ -p "$pathl" ]; then
      bad="$bad, Is a pipe"
    elif [ -b "$pathl" ]; then
      bad="$bad, Is block special"
    fi
    case $mode in
    (E)
      if [ ! -x "$pathl" ]; then
	bad="$bad, Not executable"
      fi
      if [ -d "$pathl" ]; then
	bad="$bad, Is a directory"
      fi
      ;;
    (*)
      if [ ! -r "$pathl" ]; then
	bad="$bad, Not readable"
      fi
      ;;
    esac
  fi

  if [ "$bad" ]; then
    bad="$bad."
    bad=" (${bad/#,/Problem:})"
    ret=1
  else
    exists=1
  fi

  if (( $links && ( $exists || $dump ) && ! $quiet )); then
    report=1
  fi

  # Report links.

  if (( $link && ( $report || $exec ) )); then
    # Paths in this loop are made absolute so do not need
    # to be tilde expanded.
    let hcnt=0
    declare -a hist
    if (( $report )); then
      echo "$dm$offset$ref$pathlo"
      offset="$indent$offset" 
    fi
    pathl=`absolute_path $pathl`
    while (( 1 )); do
      hist[$hcnt]="$pathl"
      let hcnt+=1
      ll=`$LS -l $pathl`
      ll=${ll##*\ } # Get target file.
      pathl=`chop_seg $pathl`
      case "$ll" in
      (/*) ref='a-> ' ; pathl="" ;; # Link is absolute path.
      (\~/*) ref='t-> ' ; ll=`expand_tilde $ll` ;; # Tilde.
      (\~*) ref='l-> ' ; ll=`expand_tilde $ll` ;; # Tilde login.
      (*) ref='r-> ' ; ll="$pathl/$ll";;
      esac
      pathl=`absolute_path $ll`
      let mcnt=0
      while (( $mcnt < ${#hist[*]} )); do
	if [ "$pathl" = "${hist[$mcnt]}" ]; then
	  echo "$dm$offset$ref$pathl (Problem: Forms a loop.)"
	  return 1
	fi
	let mcnt+=1
      done
      if [ -L "$pathl" ]; then
	if (( $report )); then
	  echo "$dm$offset$ref$pathl"
	  offset="$indent$offset" 
	fi
      else
        pathlo=$pathl
        break;
      fi
    done
    unset hist
  fi

  if (( $quiet )); then
    :
  elif (( $exists )); then
    if (( $what )); then
      pathlw=`$FILE $pathl`
      pathlw=":${pathlw#*:}"
    fi
    echo "$dm$offset${ref}$pathlo$pathlw"
    inode_check "$offset" "$pathlo"
    if (( $includes )); then
      trace_i "$offset" 'i-> ' "$pathlo"
    fi
  elif (( $dump )); then
    if (( $what && $there )); then
      pathlw=`$FILE $pathl`
      pathlw=":${pathlw#*:}"
    fi
    echo "$dm$offset${ref}$pathlo$bad$pathlw"
    ret=1
  fi

  return $ret
}

# Output verbatim text and suppress null lines.

verbatim()
{
  local temp
  while read -r temp ; do
    if [ "$temp" ]; then
      echo "$dm${offset}$ref$temp"
    fi
  done
}

# Show object archives and shared libraries.

trace_o()
{
  local pathl offset offset1 offseta ref tmp
  offset="$1"
  ref="$2"
  pathl="$3"
  offset1="$indent$offset"
  offseta="$offset"
  while read tmp; do
    if [ ! "$tmp" ]; then
      continue
    fi
    if [[ "$tmp" == *.o: ]]; then
      echo "$dm$offset$ref $tmp"
      offseta="$offset1"
      continue
    fi
    tmp=${tmp#+([[:xdigit:]])\ }
    echo "$dm$offseta$ref $tmp"
  done
}

# Show included objects.

trace_i()
{
  local pathl offset ref ret=0 incl tmp obj
  offset="$1"
  ref="$2"
  pathl="`expand_tilde $3`"
  offset="$indent$offset"
  case $mode in
  (D|S|F|[0-9])
    if [ -d "$pathl" ]; then
      for tmp in `$LS -A $pathl 2>&1`; do
	echo "$dm$offset$ref$tmp"
      done
    elif [[ "$pathl" == *.tar ]]; then
      for tmp in `$TAR -tf $pathl 2>&1`; do
	echo "$dm$offset$ref$tmp"
      done
    elif [[ "$pathl" == *.tar.gz || "$pathl" == *.tgz ]]; then
      for tmp in `$GZIP -d < $pathl | $TAR -t 2>&1`; do
	echo "$dm$offset$ref$tmp"
      done
    elif [ -x "$pathl" ]; then
      verbatim <<EOF "$offset" "$ref"
`$LDD $pathl 2> /dev/null`
EOF
    fi
    ;;
  (E)
    verbatim <<EOF "$offset" "$ref"
`$LDD $pathl 2> /dev/null`
EOF
    ;;
  (H)
    verbatim <<EOF "$offset" "$ref"
`$GREP '#include' $pathl 2> /dev/null`
EOF
    ;;
  (L)
    trace_o "$offset" "$ref" "$pathl" <<EOF
`$NM -Cg $pathl 2>&1`
EOF
    ;;
  (M)
    if [[ "$pathl" == *.gz ]]; then
      verbatim <<EOF "$offset" "$ref"
`$GZIP -d < $pathl | $GREP '^\.so' 2> /dev/null`
EOF
    else      				     
      verbatim <<EOF "$offset" "$ref"
`$GREP '^\.so' $pathl 2> /dev/null`
EOF
    fi
    ;;
  (*)
    ;;
  esac
  return $ret
}

# Trace_P() is used to search along $paths.
#
# IMPORTANT NOTES:
# 1) Uses global variables $paths and $pattern_x.
# 2) Globbing action is determined via $ref.
# 3) Correct expansion of $pattern requires file segment to be named '$file'
#    and path segment to be named '$path'.

trace_P()
{
  local offset ref path file ret=1 fnd=0 ali=0 paths_=''
  local dataf='' pattern pathl patho datag badp
  offset="$1"
  ref="$2"
  file="$3"
  # Cases where user gives a path with the filename.  In these cases
  # it points to a specific file or link so we know the path.
  case $file in
  (*/*) # A path.
    paths_="$paths"
    paths=`chop_seg $file`
    file=`last_seg $file`
    if (( $do_check )); then
      check_paths
    fi
    ;;
  (*) # Just a file.
    if [ "$ref" ]; then # Tracing an alias.
      ali=1
      eval $glob
      datag=`$LS -d $file 2> /dev/null` # Expand glob in current directory.
      eval $unglob
      if [ "$datag" ]; then
        datag=" ${datag//[[:space:]]/ }"
      else
        datag=" $file"
      fi
    fi
    ;;
  esac

  # Commands in the search path.
  for patho in $paths ; do
    path=`expand_tilde $patho`
    if (( $ali )); then
      pattern=${datag//\ / $path\/}
      dataf=`$LS -d $pattern 2> /dev/null`
    else
      eval pattern="\"$pattern_x\""
      eval $glob
      dataf=`$LS -d $pattern 2> /dev/null`
      eval $unglob
    fi

    if [ "$dataf" ]; then
      for pathl in $dataf; do
        pathl=${pathl//#$path/$patho}
	if trace_F "$offset" "$ref" "$pathl" ; then
	  fnd=1
	  ret=0
	fi
      done
    fi

    if (( ! $all && $fnd )); then
      break
    fi

  done
  if [ "$paths_" ]; then
    paths="$paths_" # Redundant, for the moment.
  fi
  return $ret
}


# Trace_R() is used to trace from reserved words onward.

trace_R()
{
  local offset ref file ret=1
  offset="$1"
  ref="$2"
  file="$3"

  if [[ " $reserveds " == *\ "$file"\ * ]]; then
    if (( ! $quiet )); then
      echo "$dm$offset$ref$Shell_a reserved word: $file"
    fi
    ret=0
  elif [[ " $functions " == *\ "$file"\ * ]]; then
    if (( ! $quiet )); then
      echo "$dm$offset$ref$Shell_a function: $file"
      if (( $what )); then
	lookup_info -f $file "    "
      fi
    fi
    ret=0
  elif [[ " $builtins " == *\ "$file"\ * ]]; then
    if (( ! $quiet )); then
      echo "$dm$offset$ref$Shell_a built in: $file"
    fi
    ret=0
  else
    trace_P "$offset" "$ref" "$file"
  fi
  return $ret
}


# Trace_A() traces from aliases onward.  This function always returns true
# if no loops were found or false otherwise.  (It is only called if an
# alias match is found.)

trace_A()
{
  local offset ref file ret=0 loop alias file_loop word
  offset="$1"
  ref="$2"
  file="$3"

  file_loop="$file" # Keep hold of file to break alias loops.
  loop=1 # Loop here looking for alias chains.
  while (( $loop )); do
    loop=0 # Once through by default.
    alias=`lookup_info -a "$file"`
    if (( $what )); then
      echo "$dm$offset$ref$Shell_a alias: $file = $alias"
    else
      echo "$dm$offset$ref$Shell_a alias: $file"
    fi
    offset="$indent$offset"
    ref='x-> '
    if (( $follow )); then
      # Extract first word from alias.
      word=${alias%%[[:space:]]*}
      word=${word#[\'\"]}
      word=${word%[\'\"]}
      if [ "$file" != "$word" ]; then
	if [[ " $file_loop " == *\ $word\ * ]]; then
	  # This is an alias loop.
	  if (( ! $quiet )); then
	    echo "$dm$offset$ref$Shell_a alias: $word (Problem: Forms a loop.)"
	  fi
	  ret=1 # Return an error status.
	elif [[ " $aliases " == *\ "$word"\ * ]]; then
	  file_loop="$file_loop $word"
	  file=$word
	  loop=1 # Need to redo the alias loop.
	else
	  trace_R "$offset" "$ref" "$word"
	fi
      else
        # This is not allowed in all shells.???
	trace_R "$offset" "$ref" "$word"
      fi
    fi
  done
  return $ret
}

# Find absolute path for the given function, even if an absolute path is
# given.  This allows which to operate even if $PATH is null.  Function
# echos the path and returns true on success.  Note that arguments are
# preserved.

find()
{
  if (( $testing )); then
    echo "find($1)" >&2
  fi
  local ret=1 funca func args='' ff=1 paths i fnd=0 warn=''
  funca=`echo $1`
  func=${funca%%@([[:space:]]*|)}
  args=${funca#$func@([[:space:]]|)}
  if [[ "$func" == */* ]]; then # Absolute path.
    if [ ! -e "$func" ]; then
      warn='was not found'
    elif [ ! -x "$func" ]; then
      warn='is not executable'
    else
      ret=0
      ff=0
    fi
    if (( $ff )); then
      if (( ! $virtual )); then
        error_all=2
      fi
      error_out "WARNING: '$funca' $warn, looking for alternative."
      func=`last_seg $funca`
    fi
  fi
  if (( $ff )); then
    ret=1
    paths="$PATH_D"
    paths="${paths//:/ }"
    for i in $paths ; do
      if [ -x "$i/$func" ]; then
        funca="$i/$func"
        if [ "$args" ]; then
          funca="$funca $args"
        fi
        ret=0
        break
      fi
    done
    if (( $ret )); then
      if (( ! $virtual )); then
        error_all=2
      fi
      error_out "WARNING: Could not find useable '$func'."
    fi
  fi
  echo "$funca"
  return $ret
}


# End of functions.


############################################################

#  Main program.

############################################################

# Defaults.  These may be over ridden from the '/etc/which.conf',
# '~/.which', 'stdin, WHICH_ENV or other file pointed to by WHICH_ENV.  You
# should arrange to set these up correctly for your system.


  bash_reserved='!  case   do  done  elif  else  esac fi for function if in select then until while { } time [[ ]]'

  bash_builtin='. : [ alias bg bind break builtin cd command continue declare dirs disown echo enable eval exec exit export false fc fg getopts hash help history jobs kill let local logout popd printf pushd pwd read readonly return set shift shopt source suspend test times trap true type typeset ulimit umask unalias unset wait'

  sh_reserved='case do done elif else esac fi for function if in select then until while'
  sh_builtin='. : [ alias bg bind break builtin cd command continue declare dirs echo enable eval exec exit export fc fg getopts hash help history jobs kill let local logout popd pushd pwd read readonly return set shift source suspend test times trap type typeset ulimit umask unalias unset wait'

  tcsh_reserved=''

  tcsh_builtin=': @ alias alloc bg bindkey break breaksw builtins case cd chdir complete continue default dirs echo echotc else end endif endsw eval exec exit fg filetest foreach glob goto hashstat history hup if jobs kill limit log login logout ls-F nice nohup notify onintr popd printenv pushd rehash repeat sched set setenv settc setty shift source stop suspend switch telltc time umask unalias uncomplete unhash unlimit unset unsetenv wait where which while'

  csh_reserved=''

  csh_builtin=': @ alias bg break case cd chdir continue dirs echo eval exec exit fg foreach glob goto hashstat history if jobs kill limit login logout nice notify onintr popd pushd rehash repeat set setenv shift source stop suspend switch time umask unalias unhash unlimit unset unsetenv wait while'

  ksh_reserved='! if then else elif fi case esac for while until do done { } function select time [[ ]]'

  ksh_builtin='alias bg break case cd continue echo eval exec exit export fc fg for function getopts hash if jobs kill let login logout newgrp print pwd read readonly return select set shift stop suspend test times trap type typeset ulimit umask unalias unset until wait whence while'

  ash_reserved='case do done elif else esac fi for function if in select then until while'

  ash_builtin='. : jobid lc setvar source alias bg bind break builtin cd command continue declare typeset dirs echo enable eval exec exit export fc fg getopts hash help history jobs kill let local logout popd pushd pwd read readonly return set shift suspend test times trap type ulimit umask unalias unset wait'

  zsh_reserved='do done esac then elif else fi for case if while function repeat time until select coproc nocorrect foreach end ! [[ { }'

  zsh_builtin='- . : [ alias autoload bg bindkey break builtin bye cd chdir command compctl continue declare dirs disable disown echo echotc emulate enable eval exec exit export false fc fg functions getln getopts hash history integer jobs kill let limit local log logout noglob popd print pushd pushln pwd r read readonly rehash return sched set setopt shift source suspend test times trap true ttyctl type typeset ulimit umask unalias unfunction unhash unlimit unset unsetopt vared wait whence where which'

# Only set these macros up in /etc/which.conf or ~/.which file if there is
# some specific reason to do so, ie if 'ps -lww' uses the wrong options on
# your system.  Best to let 'which' identify the pathnames.

LS='ls'
PS='ps -lww'
GREP='grep'
FILE='file'
LDD='ldd'
NM='nm'
TAR='tar'
GZIP='gzip'
CAT='cat'
PATH_D='/bin:/usr/bin:/usr/local/bin:/usr/ucb' # Standard places to look.

Whereis='E M I S D'

pattern0='$path/$file'
pattern1='$path/$file'
pattern2='$path/$file'
pattern3='$path/$file'
pattern4='$path/$file'
pattern5='$path/$file'
pattern6='$path/$file'
pattern7='$path/$file'
pattern8='$path/$file'
pattern9='$path/$file'

# Get any standard set up.
which_conf=${WHICH_CONF:-/etc/which.conf}
if [ -f "$which_conf" ]; then
  . "$which_conf"
fi

# User specific setup.
if [ -f ~/.which ]; then
  . ~/.which
fi

# Global variables.

Cwd=`pwd` # Our current directory.
found_all=0 # Found flag, set to 1 if failed to find anything.
error_all=0 # Error flag, set to 2 if error occured.
Shell_a='' # The actual calling shell.
Shell_i='' # How the shell is invoked.
Shellp='' # The shell path.
Shellps='' # Full pathname.
Shellfa='' # Shell options.
Shellm='' # Possible '-' in shell name as shown by ps.
Startsrc=''
Stopsrc=''
Givenrcs=0
Shell_name=0 # When set will allow shell name instead of PID.
ppid='' # PID of parent.
pppid='' # PID of grandparent.
got_shell=0 # Flag to say have parsed a ps string. 
subshell=0 # Subshell depth from shell that set up WHICH_ENV.

glob='set +f' # Leave extglob on all the time.
unglob='set -f'

unset lsi # Array for inode numbers.
inodes=' ' # String of same for faster search.

indent=" " # Indent used for each level of alias or link.
dmf=0 # Dump mode flag.
dm='' # Dump mode string.
virtual=0 # Indicates shell cannot be found.  Just fake it.

# These values are obtained from $WHICH_ENV, if any.
File_env=''

aliases=''
declare -a aliases_
builtins=''
reserveds=''
functions=''
declare -a functions_

Need_al=0 # Flag to request aliases, functions, builtins and reserveds.
Need_fu=0
Need_bu=0
Need_re=0
Need_none=1

Mode='' # List of modes to indicate what needs to be done.
mode='' # Current mode

Tilde='' # Let shell work out the details when we have identified it.
do_check=0; # Flag to check paths.
helpver=0; # Flag for help or version.

# Mode search patterns.

patternm='$path/man*/$file.*'
patternmd='$path/+(man*|cat*)/$file.*'
patterni='$path/$file[^[:alpha:]]*'
patternh='$path/$file.h $path/*/$file.h'
patternl='$path/lib$file.*'
patterns='$path/@($file)+(|[^[:alpha:]]*)'
patternd='$path/@($file)+(|[^[:alpha:]]*)'
patternf='$path/$file'
patterne='$path/$file'

# Checked paths.
path_c=''
manpath_c=''
infopath_c=''
incpath_c=''
libpath_c=''
srcpath_c=''
docpath_c=''
filpath_c=''
wh0path_c=''
wh1path_c=''
wh2path_c=''
wh3path_c=''
wh4path_c=''
wh5path_c=''
wh6path_c=''
wh7path_c=''
wh8path_c=''
wh9path_c=''

multip=0 # No names on paths.

help()
{
  echo "Usage:"
  echo "which [-$options] [--] [filename [...]]"
  echo
  echo "  Search modes:"
  echo "    -D    Search for documents along \$DOCPATH."
  echo "    -E    Search for executables along \$PATH, default mode."
  echo "    -F    Search for files along \$FILPATH."
  echo "    -H    Search for header files along \$INCPATH."
  echo "    -I    Search for info pages along \$INFOPATH."
  echo "    -L    Search for libraries along \$LIBPATH."
  echo "    -M    Search for manual pages along \$MANPATH."
  echo "    -P p  Path: Use path list <p>, only -F if used, else all modes."
  echo "    -S    Search for sources along \$SRCPATH."
  echo "    -W    Whereis mode: Implies -EMISD modes."
  echo "   -[0-9] User modes: Search on \$WH<n>nPATH."

  echo "  Shell modes:"
  echo "    -g    Given: Aliases and or builtins given on stdin."
  echo "    -n    Native: Use bash for expansions etc."
  echo "    -s    Shell: Show which shell 'which' started from."
  echo "    -x s  Cross shell:  Act as though invoked from the given shell."

  echo "  Path modes:"
  echo "    -A    Absolute: Show an absolute path, for '.', '~' etc."
  echo "    -c    Check: Check the path and report errors."
  echo "    -C    Correct: Correct the path."
  echo "    -o    Output: Space separated list of paths."
  echo "    -O    Output: Colon separated list of paths."
  echo "    -r    Reduce: Reduce all paths prior to reporting."
  echo "    -R    Relative: Report all paths relative to cwd."
  echo "    -T    Tilde: Report all paths as tilde expansions."
  echo "    -U    Unified: Show paths in unified format (no links)."
  echo "          Modes -A, -R and -T for a mutually exclusive group."

  echo "  Follow flags:"
  echo "    -e    Executable: Report the actual object used."
  echo "    -f    Follow: If an alias then follow up on first word of it."
  echo "    -i    Includes: Show any included objects."
  echo "    -l    Links: Show and follow links."
  echo "    -w    What: Report what the file is."

  echo "  Reporting flags:"
  echo "    -a    All: Show all objects with given names."
  echo "    -d    Debug: Dump some additional information."
  echo "    -h    Help: Show help and exit."
  echo "    -m    Multi: Switch on terse reporting for multiple inputs."
  echo "    -q    Quiet: Quiet mode, no output, return value only."
  echo "    -t    Terse: Report unfound objects tersely."
  echo "    -v    Version: Show version and exit."
  echo "    -V    Verbose: Show path if fail to find (old style which)."
  echo "    -z    Test: Debugging flag for developers."

  echo "  Control flags:"
  echo "    -b    Big search, append \$PATH_A, \$MANPATH_A, etc."
  echo "    -p    Path: Search path only, skip aliases, builtins, functions."
  echo "  -- or - End of options.  All following arguments are file names."
}

# When set to 1 these variables have the indicated effects.
all=0    # -a 	All: Show all objects with given names.
abs=0    # -A 	Absolute: Show an absolute path for '.' etc.
big=0    # -b   Big search, use $PATH_A etc.
check=0  # -c   Check: Check the path and report errors.
correct=0 #-C   Correct: Correct the path.
dump=0   # -d 	Debug: Dump all instances plus other information.
         # -D   Documents mode.
exec=0   # -e 	Executable: Report the actual executable used.
         # -E   Executables (normal which, default mode).
follow=0 # -f 	Follow: If an alias then follow up on first word of it.
         # -F   File mode.
given=0  # -g 	Given: Aliases and or builtins given on stdin.
         # -h 	Help: Show help and exit.
         # -H   Header file mode.
includes=0 #-i	Includes: Show any included objects.
         # -I 	Info: Info mode, search for info pages on $INFOPATH.
links=0  # -l 	Links: Show and follow links.
         # -L   Library mode.
multi=0  # -m 	Multi: Switch on terse reporting for multiple inputs.
         # -M 	Manual: Manual mode, search for man pages on $MANPATH.
native=0 # -n   Native: Use the native shell for all expansions.
pathff=0 # -p 	Path: Search path only, skip aliases and builtins.
pathg=0  # -P p Path: Use given path list.
quiet=0  # -q 	Quiet: Quiet mode, no output, return value only.
pouts=0  # -o   Output: Space separated list of paths.
poutc=0  # -O   Output: Colon separated list of paths.
reduce=0 # -r   Reduce: Reduce all paths prior to reporting.
relati=0 # -R   Relative: Report all paths relative to cwd.
shellf=0 # -s  *Shell: Show which shell 'which' started from.
         # -S   Source mode.
terse=0  # -t 	Terse: Report unfound objects tersely.
tilde=0  # -T 	Tilde: Report all paths as tilde expansions.
unified=0 #-U   Unified: Show paths in unified format (no links).
         # -v 	Version: Show version and exit.
verb=0   # -V 	Verbose: Show path if fail to find.
what=0   # -w 	What: Report what the file is.
where=0  # -W   Whereis mode.  Implies -EMISD
shell_x=0 # -x s Cross shell.  Act as though invoked from the given shell.
  x_arg=''
testing=0 # -z

options="aAbcCdDeEfFghHiIlLmMnoOpP:qrRsStTUvVwWx:z0123456789"

opt_flags=''
while getopts $options arg ; do
  case $arg in
  (a) all=1 ; relati=0 ; tilde=0 ;;
  (A) abs=1 ; do_check=1 ;;
  (b) big=1 ;;
  (c) check=1 ; do_check=1 ;;
  (C) correct=1 ; do_check=1 ;;
  (d) dump=1 ; do_check=1 ;;
  (D) Mode="$Mode D" ;;
  (e) exec=1 ;;
  (E) Mode="$Mode E" ;;
  (f) follow=1 ;;
  (F) Mode="$Mode F" ;;
  (g) given=1 ;;
  (h) helpver=1 ; help ;;
  (H) Mode="$Mode H" ;;
  (i) includes=1 ;;
  (I) Mode="$Mode I" ;;
  (l) links=1 ;;
  (L) Mode="$Mode L" ;;
  (m) multi=1 ;;
  (M) Mode="$Mode M" ;;
  (n) native=1 ;;
  (o) pouts=1 ; poutc=0 ; do_check=1 ;;
  (O) poutc=1 ; pouts=0 ; do_check=1 ;;
  (p) pathff=1 ;;
  (P) pathg=1 ; FILPATH="$OPTARG";;
  (q) quiet=1 ;;
  (r) reduce=1 ;;
  (R) relati=1 ; all=0 ; tilde=0 ; do_check=1 ;;
  (s) shellf=1 ;;
  (S) Mode="$Mode S" ;;
  (t) terse=1 ;;
  (T) tilde=1 ; all=0 ; relati=0 ; do_check=1 ;;
  (U) unified=1 ; do_check=1 ;;
  (v) helpver=1 ; echo "which $version" ;;
  (V) verb=1 ;;
  (w) what=1 ;;
  (W) where=1; Mode="$Mode $Whereis" ;;
  (x) shell_x=1 ; x_arg="$OPTARG" ;;
  (z) testing=1 ;;
  ([0-9]) Mode="$Mode $arg" ;;
  (*) ;;
  esac
  opt_flags="$opt_flags $arg"
done
# Remove options from arg list.
shift $[$OPTIND - 1]

# Manipulate flags for defaults and special cases.

opt_flags=${opt_flags//\ b/}
opt_flags=${opt_flags//\ d/}
opt_flags=${opt_flags//\ g/}
opt_flags=${opt_flags//\ m/}
opt_flags=${opt_flags//\ t/}
opt_flags=${opt_flags//\ V/}
opt_flags=${opt_flags//\ x/}
opt_flags=${opt_flags//\ z/}
opt_flags=${opt_flags//\ n/}
opt_flags=${opt_flags//\ h/}
opt_flags=${opt_flags//\ v/}

if (( ! $# )) && [ ! "$opt_flags" ]; then
  if (( $helpver )); then
    error_all=(( $found_all + $error_all ))
    exit $error_all
  fi
  # Default operation.
  shellf=1
fi

# Default mode.
if [ ! "$Mode" ]; then
  Mode='E'
else
  # Multiple mode operation, remove duplicate flag entries.
  got=''
  for i in $Mode ; do
    if [[ "$got" != *$i* ]]; then
      got="$got $i"
    fi
  done
  Mode="$got"
fi

if [[ "$Mode" = *E* ]] && (( ! $pathff && $# )) ; then
  Need_al=1
  Need_fu=1
  Need_bu=1
  Need_re=1
  Need_none=0
fi
  
# Reporting on paths.
if (( $terse || $verb || ( ${#Mode} > 2 && $multi ) )); then
  multip=1
fi

# Reporting on modes.
if (( ${#Mode} > 2 && $dump )); then
  dmf=1
fi
# Terse reporting on objects.
if (( $multi && ( $# > 1 || ( $# >= 1 && ${#Mode} > 2 ) ) )); then
  terse=1
fi

# Combination of -el makes no sense.
if (( $links )); then
  exec=0
fi

# Quiet takes precedence.
if (( $quiet )); then
  dump=0
  shellf=0
fi

ppid=$PPID

# First make sure that the tools can be found.
if [ ! "$LS" ]; then
  LS='ls'
fi
LS=`find "$LS"`

if [ ! "$CAT" ]; then
  CAT='cat'
fi
CAT=`find "$CAT"`

if (( ! $shell_x )); then
  if [ ! "$PS" ]; then
    PS='ps -lww'
  fi
  PS=`find "$PS"`
  
  if [ ! "$GREP" ]; then
    GREP='grep'
  fi
  GREP=`find "$GREP"`
fi

if (( $what || $dump)); then
  if [ ! "$FILE" ]; then
    FILE='file'
  fi
  FILE=`find "$FILE"`
fi

if (( $includes )); then
  if [ ! "$LDD" ]; then
    LDD='ldd'
  fi
  LDD=`find "$LDD"`
  if [ ! "$NM" ]; then
    NM='nm'
  fi
  NM=`find "$NM"`
  if [ ! "$TAR" ]; then
    TAR='tar'
  fi
  TAR=`find "$TAR"`
  if [ ! "$GZIP" ]; then
    GZIP='gzip'
  fi
  GZIP=`find "$GZIP"`
fi

if (( $shell_x )); then
  check_shell "$x_arg"
elif (( $native )); then
  get_psdata
else
  get_psdata
  if (( $given )); then
    get_stdin_info
  elif [ "${WHICH_ENV:-}" ]; then
    get_env_info "$WHICH_ENV"
  fi
  if [ "$File_env" ]; then
    Shell_name=1
    get_file_info
  fi
  Shellps=`find $Shellps`
  Shellp=`chop_seg $Shellps`
fi

if [ ! "$Shell_a" ]; then
  id_shell "$Shellps"
fi

report_shell

if (( $dump && $where )); then
  echo "Whereis = '$Whereis'"
fi

Tilde=`expand_tilde '~'` # Let shell work out the details.

if (( ! $Need_none )); then
  # Only need this if there are some files to identify or path reporting.
  # Tcsh does not define reserveds and bash has no way to report them, so
  # if they are not defined there is no point in synthesising a shell to
  # get them (this is a big hit on speed).  Just rely on default information.
  #  "$reserveds" -o 
  if (( ! $virtual )) && [ ! "$builtins" ]; then
    get_synth_info
  fi

  get_default_info
fi

if (( $testing )); then
  show_info
fi

# Analyse paths.

for mode in $Mode ; do

  case $mode in
  (E)
    paths="$PATH"
    paths_a="$PATH_A"
    pathn="path_c"
    nnnn='PATH'
    ;;
  (M)
    paths="$MANPATH"
    paths_a="$MANPATH_A"
    pathn="manpath_c"
    nnnn='MANPATH'
    ;;
 (I)
    paths="$INFOPATH"
    paths_a="$INFOPATH_A"
    pathn="infopath_c"
    nnnn='INFOPATH'
    ;;
  (H)
    paths="$INCPATH"
    paths_a="$INCPATH_A"
    pathn="incpath_c"
    nnnn='INCPATH'
    ;;
  (L)
    paths="$LIBPATH"
    paths_a="$LIBPATH_A"
    pathn="libpath_c"
    nnnn='LIBPATH'
    ;;
  (S)
    paths="$SRCPATH"
    paths_a="$SRCPATH_A"
    pathn="srcpath_c"
    nnnn='SRCPATH'
    ;;
  (D)
    paths="$DOCPATH"
    paths_a="$DOCPATH_A"
    pathn="docpath_c"
    nnnn='DOCPATH'
    ;;
  (F)
    paths="$FILPATH"
    paths_a="$FILPATH_A"
    pathn="filpath_c"
    nnnn='FILPATH'
    ;;
  ([0-9])
    eval paths="\$WH${mode}PATH"
    eval paths_a="\$WH${mode}PATH_A"
    pathn="wh${mode}path_c"
    nnnn="WH${mode}PATH"
    ;;
  esac

  if (( $shell_x && ! $virtual && ! $native )); then
    # The appropriate <shell>_synth_info() has been run by now and
    # Startsrc and Stopsrc are set up.
    # Shell may inherit path from this shell or we may force shell to
    # set its own variables.  Choose the latter.
    eval "$nnnn=''"
    export $nnnn
    paths=`$Shellps -s << EOF1 2> /dev/null
    $Startsrc
    echo \\\$$nnnn
    $Stopsrc
# Do not adjust the following two lines.
EOF1
    `
    export -n $nnnn
  fi

  if (( $pathg )) && [[ "$Mode" != *F* ]]; then
    paths="$FILPATH"
    paths_a="$FILPATH_A"
    pathn="filpath_c"
    nnnn='FILPATH'
  fi

  pathor="$paths"
  paths=${paths/#:/.:}
  paths=${paths/%:/:.}
  paths=${paths//::/:.:}
  paths=${paths//::/:.:} # In case of any ':::' sequences.
  paths=${paths//:/\ }
# Should check with $pathor????

  if (( $big )); then
    # This is not quite the action required.  Should handle extra paths
    # in paths_a separately and delineate output for the two cases. ????
    if [ "$paths_a" ]; then
      paths_a=${paths_a/#:/.:}
      paths_a=${paths_a/%:/:.}
      paths_a=${paths_a//::/:.:}
      paths_a=${paths_a//::/:.:} # In case of any ':::' sequences.
      paths_a=${paths_a//:/\ }
      paths_a=" $paths_a "
      for ptmp in $paths ; do
        if [[ "$paths_a" == *\ $ptmp\ * ]]; then
          paths_a=${paths_a//\ $ptmp}
        fi
      done
      paths="$paths${paths_a%\ }"
    fi
  fi

  if (( $do_check )); then
    check_paths -u
  fi

  eval $pathn="\$paths"
  if (( ( $poutc || $pouts ) && ! $quiet )); then
    if (( $multip || $dump )); then
      echo "\$$nnnn:"
    fi
    if (( $pouts )); then
      echo "$paths"
    elif (( $poutc )); then
      echo "${paths//\ /:}"
    fi
  fi
done

# Main loop

for file in $* ; do

  for mode in $Mode ; do

   if (( $dmf )); then
     dm="($mode) "
   fi

    case $mode in
    (E)
      paths="$path_c"
      pattern_x="$patterne"
      ;;
    (M)
      paths="$manpath_c"
      if (( $dump )); then
	pattern_x="$patternmd"
      else
	pattern_x="$patternm"
      fi
      ;;
    (I)
      paths="$infopath_c"
      pattern_x="$patterni"
      ;;
    (H)
      paths="$incpath_c"
      pattern_x="$patternh"
      ;;
    (L)
      paths="$libpath_c"
      pattern_x="$patternl"
      ;;
    (S)
      paths="$srcpath_c"
      pattern_x="$patterns"
      ;;
    (D)
      paths="$docpath_c"
      pattern_x="$patternd"
      ;;
    (F)
      paths="$filpath_c"
      pattern_x="$patternf"
      ;;
    ([0-9])
      eval paths="\$wh${mode}path_c"
      eval pattern_x="\$pattern$mode"
      ;;
    esac

    if (( $pathg )) && [[ "$Mode" != *F* ]]; then
      paths="$filpath_c"
    fi

    fnd=0

    # First we look for aliases.  Funnily enough a string like '../xxx' can
    # be an alias, so we look here even if a path was given.
    if [[ $pathff -ne 1 && "$mode" == "E" ]]; then

      if [ "$aliases" ] && [[ " $aliases " == *\ $file\ * ]]; then
	if (( ! $quiet )); then
	  for xxx in $aliases ; do
	    if [[ "$xxx" == $file ]]; then
              trace_A '' '' "$xxx"
	    fi
	  done
	fi
	fnd=1
      fi

      if (( $all || ! $fnd )); then
	if [ "$reserveds" ] && [[ " $reserveds " == *\ $file\ * ]]; then
	  if (( ! $quiet )); then
	    for xxx in $reserveds ; do
	      if [[ "$xxx" == $file ]]; then
		echo "$dm$Shell_a reserved word: $xxx"
	      fi
	    done
	  fi
	  fnd=1
	fi
      fi

      # We do not currently follow functions.
      if (( $all || ! $fnd )); then
	if [ "$functions" ] && [[ " $functions " == *\ $file\ * ]]; then
	  if (( ! $quiet )); then
	    for xxx in $functions ; do
	      if [[ "$xxx" == $file ]]; then
		echo "$dm$Shell_a function: $xxx"
		if (( $what )); then
		  lookup_info -f $xxx "    "
		fi
	      fi
	    done
	  fi
	  fnd=1
	fi
      fi

      if (( $all || ! $fnd )); then
	if [ "$builtins" ] && [[ " $builtins " == *\ $file\ * ]]; then
	  if (( ! $quiet )); then
	    for xxx in $builtins ; do
	      if [[ "$xxx" == $file ]]; then
		echo "$dm$Shell_a built in: $xxx"
	      fi
	    done
	  fi
	  fnd=1
	fi
      fi

    fi # End of skip.

    if (( $all || ! $fnd )); then
      if trace_P '' '' "$file" ; then
        fnd=1
      fi
    fi

    if (( ! $fnd )); then
      found_all=1 # Failed to find a file.
      if (( ! $quiet )); then
	if (( $verb )); then
  	  pathe=${paths//\ /:}
	  echo "$file not found in: $pathe"
	elif (( $terse )); then
	  echo "Not Found: ($mode)-> $file"
  	fi
      fi
    fi

  done # For each mode.

done # For each file.

error_all=(( $found_all + $error_all ))
exit $error_all

# End.
