Example script: checkpath.ksh


   1: #!/bin/ksh
   2: # 
   3: # Check elements of PATH for accessibilty, and for possibly conflicting program names.
   4: #
   5: # $Header: /afs/northstar/ufac/richard/bin/RCS/checkpath,v 1.5 2009/02/07 08:41:15 richard Exp $
   6: #
   7: # This script checks all the directories in $PATH for accessibility, then it checks specific
   8: # named arguments for possible conflicts between different directories in the $PATH.
   9: # If no arguments are named, all programs in the $PATH are examined for conflicts.
  10: #
  11: # Directories which are equivalent (symlinks) are removed from the list before the 
  12: # file analysis is made, to cut down on spurious conflicts.  Apparent conflicts which are symlinks
  13: # to the same file are also not reported.  Most systems seem to have many of these.
  14: #
  15: # This cannot get all possible conflicts for all shells and all situations.
  16: # Specifically, it does not address shell aliases, built-ins or global shell functions,
  17: # all of which are shell-dependant.  
  18: # Nor can it address temporary conflicts caused by a startup script which augments $PATH
  19: # and then spawns child processes which perform a $PATH search.
  20: #
  21: # If "." is in the path, and the current directory also happens to be in the path, spurious
  22: # conflicts are not reported because of the path trimming performed on equivalent directories.
  23: #
  24: # Warning: A path element containing "~" is not valid in sh/ksh, but is valid in bash and some csh/tcsh.  
  25: # Normally the "~" is expanded when the path is set.  We test (with "-d") for the presence of a directory
  26: # and this fails because "~" is not expanded inside the test operation.  In bash, the test fails, 
  27: # the "~" still works as a path element.  It is most reliable to use $HOME explicitly, not "~", in $PATH
  28: #
  29: # Options:
  30: #  -v verbosity  
  31: #      0 - terse output if conflicts are found.
  32: #      1 - medium output if conflicts are found (default)
  33: #      2 - long output. List all potential conflicts, even if multiple pathnames resolve to the same file.
  34: #          Follow symlinks to their final destinations.  Run 'file' on each conflicting pathname.
  35: #      3 - Additional debugging information
  36: #  -d     directory check only - don't analyse any filenames
  37: #  -p     path check only, no directory report.
  38: #
  39: # Exit status:
  40: #   0 = all directories in PATH are accessible. 
  41: #  >0 = a count of the inaccessible directories. 
  42: #       (the exit status does not reflect whether pathname conflicts were discovered)
  43: #
  44: # 2004/11/03 Richard Brittain, Dartmouth College Computing Services.
  45: 
  46: # Collect options and set defaults
  47: 
  48: verbosity=1
  49: dircheckonly=0
  50: pathcheckonly=0
  51: while getopts pdv: o ; do
  52:    case $o in
  53:    p) pathcheckonly=1;;
  54:    d) dircheckonly=1;;
  55:    v) verbosity=$OPTARG;;
  56:    esac
  57: done
  58: shift $OPTIND-1
  59: 
  60: # Functions for use later - control starts near the end of the script
  61: 
  62: wordsplit()
  63: {
  64:    # Take a string in $1, a set of delimiters in $2, and print the token
  65:    # indexed by $3
  66:    # Set noglob in the calling routine to avoid expanding wildcards in the result
  67:    typeset arg=$3
  68:    IFS=$2 ; set -- $1
  69:    eval print -R \${$arg}
  70: }
  71: 
  72: 
  73: verboseprint()
  74: {
  75:    # Print erguments if $verbosity (global) is set high enough
  76:    threshold=$1; shift
  77:    if [[ $verbosity -ge $threshold ]]; then
  78:       for arg in "$@"; do
  79:          print "$arg"
  80:       done
  81:    fi
  82: }
  83: 
  84: 
  85: follow_links()
  86: {
  87:    (
  88:    # Run in a subshell since we need to change directories
  89:    # Follow the symlinks in $1 and return the final location.
  90:    val=$1
  91:    line=$(ls -ld $val)
  92:    link=$(wordsplit "$line" " " 11)
  93:    # $link now contains something if there is a link
  94:    while [ -n "$link" ]; do
  95:       # $val is the full pathname of the file we searched on
  96:       # $link is the linked-to name (path may be relative or absolute)
  97:       # $line is the output of ls -ld $val
  98:       # change directories to the location of $val, and try again
  99:       # Note that dirname is not the same as ${val%/*} if there is only one /
 100:       cd $(dirname $val)
 101:       # if $link is a relative pathname, stick it onto current directory
 102:       # otherwise, use it as an absolute name
 103:       case $link in
 104:       /*) val=$link ;;
 105:       *)  case $PWD in
 106:           /) val=/$link ;;
 107:           *) val=$PWD/$link ;;
 108:           esac
 109:           ;;
 110:       esac
 111:       line=$(ls -ld $val)
 112:       # now see if we have another link
 113:       link=$(wordsplit "$line" " " 11)
 114:    done
 115:    # no [more] links - just return the final pathname
 116:    print $val
 117:    )
 118: }
 119: 
 120: check_equivs()
 121: {
 122:    # Check the path $1 for equivalence with each of the remaining arguments.
 123:    # Echo all that match
 124:    p=$1; shift
 125:    for d in "$@" ; do
 126:       [ $p -ef $d ] && print -R "$d"
 127:    done
 128: }
 129: 
 130: 
 131: elim_equivs()
 132: {
 133:    # Check the path $1 for equivalence with each of the remaining arguments
 134:    # Echo the ones that do NOT match (i.e., are unique)
 135:    # If less than two arguments, echo the argument and return
 136:    if [ $# -lt 2 ]; then
 137:       print -R "$1"
 138:    else
 139:       p=$1; shift
 140:       for d in "$@"; do 
 141:          [ ! $p -ef $d ] && print -R "$d"
 142:       done
 143:    fi
 144: }
 145: 
 146: listdirs()
 147: {
 148:    # list all the files in each directory argument.  No directory parts or headers
 149:    for dir in "$@"; do
 150:       # arguments should be clean, but double check
 151:       [ -d "$dir" ] && ls -1 $dir
 152:    done
 153: }
 154: 
 155: check_path()
 156: {
 157:    # For each directory in a list passed as $1 ($PATH format), make sure the
 158:    # directory exists, and is read/execute
 159:    # If a directory is a symlink, check whether the linked-to directory is also in the path.
 160: 
 161:    # $pathels is created as a space-separated list of path elements, with duplicates 
 162:    # (e.g. symlinks) and inaccessible elements removed.  This is returned to the caller 
 163:    # as a global variable.
 164:    pathels=
 165: 
 166:    # Uses global $verbosity to control messages to stdout.
 167:    # For each path element in turn, output is 
 168:    # "seq#: directory [errors or warnings]"
 169: 
 170:    verboseprint 1 "Path directories, in search order\n"
 171:    status=0
 172:    seq=0
 173: 
 174:    path=$1
 175:    OIFS=$IFS; IFS=:; set $path; IFS=$OIFS
 176:    for dir in "$@"; do
 177:       seq=$((seq + 1))
 178:       verboseprint 1 "$seq: $dir\c"
 179:       if [[ -L $dir ]]; then
 180:          linked_dir=$(follow_links $dir)
 181:          verboseprint 1 "  \tWARNING: $dir symlinks to $linked_dir\c"
 182:       fi
 183:       if [[ ! -d $dir ]]; then
 184:          verboseprint 1 "  \tERROR: Missing directory\c"
 185:          status=$((status + 1))
 186:       elif [[ ! ( -x $dir && -r $dir ) ]]; then
 187:          # Note - directories owned by the current user always seem to pass this test
 188:          # regardless of permissions
 189:          verboseprint 1 "  \tERROR: Inaccessible directory: check permissions\c"
 190:          status=$((status + 1))
 191:       else 
 192:          # No access problems, but check for duplicates (symlinks or real duplicates)
 193:          # and do not add those to $pathels or we'll get bogus conflicts.
 194:          equivdir=$(check_equivs "$dir" $pathels)
 195:          if [ "$equivdir" ] ; then
 196:             verboseprint 1 "  (equivalent to $equivdir, already in PATH)\c"
 197:          else
 198:             pathels="$pathels $dir"
 199:          fi
 200:       fi
 201:       # Generate a newline - any mesages above are on the same line.
 202:       verboseprint 1 ""
 203:       # Debugging - show the directory details, but suppress errors from missing directories etc.
 204:       [ $verbosity -gt 1 ] && ls -ld $dir 2>/dev/null
 205:    done
 206:    # output spacing only.
 207:    verboseprint 1 "" 
 208:    # Return an exit status indicating bad path elements.
 209:    return $status
 210: }
 211: 
 212: 
 213: searchpath()
 214: {
 215:    # Look for the program given as $1 in each of the directories given in the remaining arguments.
 216:    # Print warning messages to stdout for each real conflict.  Ignore apparent conflicts which
 217:    # actually resolve to the same file.
 218:    # Return an exit status which is the number of real conflicts located.
 219:    [ $verbosity -ge 4 ] && set -x
 220:    prog=$1; shift
 221:    confpaths=
 222:    nconf=0
 223:    for dir in "$@"; do
 224:       if [[ -f $dir/$prog && -x $dir/$prog ]]; then
 225:          confpaths="$confpaths $dir/$prog"
 226:          nconf=$((nconf + 1))
 227:       fi
 228:    done
 229:    # We have a list of $nconf items, but some may be equivalent to others.  We need to
 230:    # eliminate the duplicates and return the number of real conflicts.  The list can be
 231:    # empty or have just one item, in which case we have no conflicts, but may want to 
 232:    # present the file details anyway
 233:    
 234:    if [ $nconf -eq 0 ]; then
 235:       # Could get here if the user specified a program name which doesn't exist
 236:       # OR, files appear in the path but are not executable.
 237:       verboseprint 1 "$prog not found in \$PATH"
 238:    elif [ $nconf -eq 1 ]; then
 239:       # Found the program, but only once - don't report anything.
 240:       return 0
 241:    else
 242:       # We have two or more potential pathnames in conflict
 243:       # Detect linked files.  Do not count paths which resolve to the same file 
 244:       # Reset the arguments to the function, for easier parsing
 245:       rconf=0
 246:       rconfpaths=
 247:       set -- $confpaths
 248:       p1=$1; shift
 249:       remainder=$(elim_equivs "$p1" "$@")        
 250:       while [ -n "$remainder" ]; do
 251:          rconfpaths="$rconfpaths $p1"
 252:          rconf=$((rconf + 1))
 253:          p1=$1
 254:          [ $# -gt 0 ] && shift
 255:          remainder=$(elim_equivs "$p1" "$@")
 256:       done
 257:       # $rconf now contains a count of the non-equivalent pathnames, which may be 0 (no real conflicts)
 258:       if [ $rconf -eq 0 ] ; then
 259:          # No real conflicts, but print the info anyway if we are being verbose
 260:          verboseprint 2 "$prog has 0 conflicts"
 261:          if [ $verbosity -ge 2 ]; then
 262:             set -- $confpaths
 263:             for path in "$@"; do
 264:                print "0: \c" ; ls -l $path
 265:                if [[ -L $path ]] ; then
 266:                   print "       -> $(follow_links $path)"
 267:                fi
 268:             done
 269:             print
 270:             for path in "$@"; do
 271:                if [[ -L $path ]]; then
 272:                   print -R "-> $(file $(follow_links $path))"
 273:                else
 274:                   print -R "   $(file $path)"
 275:                fi
 276:             done
 277:             print   
 278:          fi
 279:          return 0
 280:       else
 281:          # We have 2 or more real conflicts - list them, with 'ls -l' and 'file' output
 282:          verboseprint 0 "$prog has $rconf conflicts"
 283:          if [ $verbosity -ge 1 ]; then
 284:             # At this point, $rconfpaths has the conflicting pathnames in order, so we should
 285:             # be able to do "ls -l $rconfpaths".  However, 'ls' sometimes generates output not in the
 286:             # same order as the arguments, so step through explicitly and add a counter.
 287:             set -- $rconfpaths
 288:             i=0
 289:             for path in "$@"; do
 290:                print "$i: \c" ; ls -l $path
 291:                if [[ $verbosity -ge 2 && -L $path ]] ; then
 292:                   print "       -> $(follow_links $path)"
 293:                fi
 294:                i=$((i+1))
 295:             done
 296:             print
 297:             # repeat for the 'file' information
 298:             for path in "$@"; do
 299:                if [[ $verbosity -ge 2 && -L $path ]] ; then
 300:                   print -R "-> $(file $(follow_links $path))"
 301:                else
 302:                   print -R "   $(file $path)"
 303:                fi
 304:             done
 305:             print
 306:          fi
 307:       fi
 308:       return $rconf
 309:    fi
 310: }
 311: 
 312: 
 313: # Control starts here.
 314: 
 315: # First check once that all the directories in the $PATH exist and are read/execute.
 316: # This is always performed, to validate the path elements for later,
 317: # but the report to stdout may be suppressed
 318: if [ $pathcheckonly -eq 1 ]; then
 319:    check_path $PATH >/dev/null
 320:    estat=$?
 321: else
 322:    check_path $PATH; estat=$?
 323:    estat=$?
 324: fi
 325: 
 326: verboseprint 3 "Modified path elements:" "$pathels"
 327: 
 328: # Next check all the named arguments, or default to all the executables in the path.
 329: 
 330: dupcount=0
 331: if [ $dircheckonly -ne 1 ]; then
 332:    if [ $# -gt 0 ]; then
 333:       # Examine specific programs named as arguments
 334:    
 335:       for pathname in "$@";  do
 336:          prog=${pathname##*/}
 337:          searchpath $prog $pathels
 338:          [ $? -gt 0 ] && dupcount=$((dupcount + 1))
 339:       done
 340:    else
 341:       # No arguments given - analyse all the directories on the path.
 342:       # The pattern to grep has a space and a tab.
 343:       totcount=$(listdirs $pathels | wc -l)
 344:       listdirs $pathels | sort | uniq -c | grep -v '^ *1[ 	]' | while read ndups conflict ; do
 345:          # searchpath function will check $conflict against $pathels and print messages to stdout.
 346:          # The exit status is the number of real conflicts found (not counting symlinks to the same file)
 347:          # By using $pathels we already eliminate most of the spurious conflicts caused by the same
 348:          # directory appearing the in the path multiple times.
 349:          searchpath $conflict $pathels
 350:          [ $? -gt 0 ] && dupcount=$((dupcount + 1))
 351:       done
 352:       verboseprint 1 "Total files examined:  $totcount" 
 353:    fi   
 354:    verboseprint 1 "Total conflicting names:  $dupcount"
 355: fi
 356: exit $estat



  last modified 22/03/2012 Introduction Table of Contents
(frame/no frame)
Printable
(single file)
© Dartmouth College