home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / fixnames < prev    next >
Encoding:
Korn shell script  |  1997-08-26  |  15.1 KB  |  467 lines

  1. #!/bin/ksh
  2. # @(#) fixnames.ksh 1.1 95/07/30
  3. # 95/07/16 John H. DuBois III (john@armory.com)
  4. # 95/07/20 Catch filenames that start with -
  5. # 95/07/21 Deal with bad symlinks & names that contain whitespace correctly.
  6. #          Print filenames that contain \ correctly.  Added xH options.
  7. # 95/07/30 Added r option.
  8.  
  9. name=${0##*/}
  10. Usage="Usage: $name [-hHilnr] [-c<character-list>] [name ...]"
  11. ExpandDirs=true
  12. ReadInput=false
  13. ListOnly=false
  14. Debug=false
  15. Recurse=false
  16.  
  17. ### Argument processing
  18.  
  19. while getopts :hlinc:rxH opt; do
  20.     case $opt in
  21.     h)
  22.     echo \
  23. "$name: deal with filenames that contain problematic characters.
  24. $name generates a file list from the list of file and directory names given
  25. on the command line, expanding directory names into a list of the files they
  26. contain.  If no names are given, the file list is generated from the contents
  27. of the current working directory.  The names in the list are then checked for
  28. the existance of characters which may be difficult to enter at the command
  29. line, due to lack of keyboard support or because they are special to one of the
  30. standard shells.  Any names that contain such characters are presented to the
  31. user, with options given to rename or remove them.  When names are printed, any
  32. unprintable characters are presented in ^X (for characters below ASCII 128) or
  33. octal (for characters >= ASCII 128) notation.
  34. $Usage
  35. Options:
  36. -c<character-list>: Add the given characters to the list of legal characters.
  37.     The existance of these characters in a filename will then not cause the
  38.     filename to be considered a problem.
  39. -h: Print this help.
  40. -H: Print a description of the problems that certain characters can cause.
  41. -i: Read a list of filenames from the standard input, one per line.  Directory
  42.     names are not expanded into the files they contain.  NOTE -- filenames
  43.     containing newlines cannot be read from the standard input!
  44. -r: Recursively search any of the named files that are directories, or the
  45.     current directory if no names are given, searching for problem names.
  46. -l: List problem filenames along with information about the files, but do not
  47.     do anything to them.
  48. -n: Do not expand directory names given on the command line into the files
  49.     they contain.  This allows the directory names themselves to be acted on."
  50.     exit 0
  51.     ;;
  52.     H)  print -r \
  53. "The following lists the characters considered to be problem characters when
  54. used in filenames, and the reasons they cause difficulties:
  55. \"Non-printing\" characters (most of those with ASCII values below 33 and above
  56.   126) will print in a manner dependent on the display, possibly not printing
  57.   at all or being interpreted as screen control characters.  Some are difficult
  58.   to enter at a keyboard because they have special meaning to the serial driver
  59.   or line discipline.
  60. Tab and space may be confused with each other, and require quoting when entered
  61.   at the command line.
  62. Newline requires quoting, is difficult to quote in some shells, and confuses
  63.   any utility that expects a newline-separated list of filenames.
  64. ! is special to csh and some other shells, with its special meaning having a
  65.   high enough precedence that it can be difficult for novices to escape.
  66. # is the comment character, special to the shells when appearing at the start
  67.   of a word.
  68. ~ is the home directory expansion character, special to csh, ksh, and some
  69.   other shells when appearing at the start of a word.
  70. - at the start of an argument list is special to most commands.
  71. *?[]<>|$&();\` are used for filename globbing, IO redirection, variable
  72.   expansion, backgrounding, subshell statements, statement separation, and
  73.   command line substitution.  They must be be quoted or escaped.
  74. \"'\\ are quoting and escape characters, and can be tricky to quote or escape.
  75. {} are used to generate argument lists in csh, ksh, and some other shells.
  76. ^ is the old pipe character, still used by sh.
  77. Characters with ASCII values above 128 cannot be entered at most keyboards
  78.   without special support, and have no standard display representation."
  79.     exit 0;
  80.     ;;
  81.     c)
  82.     ExtraChars="$OPTARG"
  83.     ;;
  84.     l)
  85.     ListOnly=true
  86.     ;;
  87.     n)
  88.     ExpandDirs=false
  89.     ;;
  90.     i)
  91.     ReadInput=true
  92.     ;;
  93.     x)
  94.     Debug=true
  95.     ;;
  96.     r)
  97.     Recurse=true
  98.     ;;
  99.     +?)
  100.     print -u2 "$name: options should not be preceded by a '+'."
  101.     exit 1
  102.     ;;
  103.     :) 
  104.     print -r -u2 -- \
  105.     "$name: Option '$OPTARG' requires a value.  Use -h for help."
  106.     exit 1
  107.     ;;
  108.     ?)
  109.     print -u2 "$name: $OPTARG: bad option.  Use -h for help."
  110.     exit 1
  111.     ;;
  112.     esac
  113. done
  114.  
  115. # remove args that were options
  116. let OPTIND=OPTIND-1
  117. shift $OPTIND
  118.  
  119. ### Function definitions
  120.  
  121. # uSelect.ksh 1.0 95/01/02
  122. # 95/01/02 john h. dubois iii (john@armory.com)
  123.  
  124. # Usage: uSelect prefix prompt-string word ...
  125. # uSelect presents the words in a select list.
  126. # If prefix is non-null, it is printed before the select list.
  127. # If prompt-string is null, a default prompt is used.
  128. # Each word must have at least one capital letter in it.
  129. # A word may be selected by number or by entering the first capital letter
  130. # in it (in upper or lower case).  The letter (always capitalized) is returned
  131. # in the global var uSelect_ret.  If more than one word is entered, the extra
  132. # words are returned in the global var uSelect_params.
  133. # If a word contains '#', the part before the '#' is used in the select list.
  134. # If no extra parameters are entered, then the part after the '#' is used as a
  135. # secondary prompt, and the words entered in response are assigned to
  136. # uSelect_params.  The selection fails if no words are entered.
  137. # The entire selection selection string is returned in the global variable
  138. # uSelect_selected.
  139. # The return status is the number of the word selected (starting with 1),
  140. # or 0 if EOF or 'q' is entered.
  141. # Example usage:
  142. #     uSelect "Current file: $file" "" "View" "Move#Move to:" && Finish
  143. #     case "$uSelect_ret" in
  144. #         V)  foo "$file";;
  145. #         M)  [ -n "$uSelect_params" ] && $mv -- "$file" "$uSelect_params"
  146. #     esac
  147. # This would produce the display:
  148. #     Current file: bsize
  149. #     1) View
  150. #     2) Move
  151. #     Select by # or letter (hit return for options); use Q to quit:
  152. # The final line would be replaced by the prompt-string if one was given.
  153.  
  154. typeset -uL1 uSelect_ret
  155. function uSelect {
  156.     typeset PS3 Cmd word letters= Options FullOpts Prefix="
  157. $1"
  158.     typeset -uL1 l
  159.     typeset -u U
  160.     typeset -i CmdNum=1
  161.  
  162.     # Quit is not added as a regular menu item so that one more regular item
  163.     # will fit on the screen.  Instead, list it in prompt.
  164.     # Had to shorten this prompt because ksh wouldn't let it be longer!
  165.     [ -n "$2" ] && PS3=$2 || PS3=\
  166. "Select by # or letter (hit return for options); use Q to quit: "
  167.     shift 2
  168.     set -A FullOpts -- "" "$@"
  169.     for word; do
  170.     Options[CmdNum]=${word%#*}
  171.     l=${word##*([!A-Z])}
  172.     letters=$letters$l
  173.     let CmdNum+=1
  174.     done
  175.     # Do the elif test in two [[]] so that the one eval'ed is only done if
  176.     # $REPLY is an upper-case letter
  177.     # Use -r wherever Prefix is used because it might contain \ sequences
  178.     # that should be printed literally.
  179.     print -r -u2 "$Prefix"
  180.     select Cmd in "${Options[@]}"; do
  181.     set -- $REPLY
  182.     U=$1
  183.     [ "$U" = Q ] && return 0    # Quit option was selected by letter
  184.     if [ -n "$Cmd" ]; then
  185.         CmdNum=$1
  186. #        # If Quit option was selected by number...
  187. #        [ CmdNum -gt ${#Options[*]} ] && return 0
  188.         uSelect_ret=${Cmd##*([!A-Z])}
  189.     elif [[ "$1" = [a-zA-Z] ]] && eval [[ "$letters" = "*$U*" ]]; then
  190.         uSelect_ret=$U
  191.         letters=${letters%%$U*}
  192.         CmdNum=${#letters}+1
  193.     else
  194.         print -u2 "Invalid selection."
  195.         print -r -u2 "$Prefix"
  196.         continue
  197.     fi
  198.     shift
  199.     uSelect_params="$*"
  200.     word=${FullOpts[CmdNum]}
  201.     if [[ -z "$uSelect_params" && "$word" = *#* ]]; then
  202.         print -nr -u2 "${word##*#} "
  203.         read
  204.         if [ -z "$REPLY" ]; then
  205.         print -u2 "Must give a non-null response."
  206.         print -r -u2 "$Prefix"
  207.         continue
  208.         fi
  209.         uSelect_params=$REPLY
  210.     fi
  211.     uSelect_selected=${Options[CmdNum]}
  212.     return $CmdNum
  213.     done
  214.     return 0
  215. }
  216.  
  217. # Can't pipe names through awk because before uncontrol they might have
  218. # embedded newlines.  So, must pass all names on cmd line.  Use a separate
  219. # invokation for each filename so that argument space isn't exceeded;
  220. # using this utility on current dir might generate a very large name list.
  221. function Uncontrol {
  222. awk '
  223. BEGIN {
  224.     MakeUncontrolTable()
  225.     print Uncontrol(ARGV[1])
  226. }
  227. # @(#) uncontrol.awk 92/11/09
  228. # Uncontrol(S): Convert control characters in S to symbolic form.
  229. # Characters in S with values < 32 and with value 128 are converted to the form
  230. # ^X.  Characters with value >= 128 are converted to the octal form \0nnn.
  231. # The resulting string is returned.
  232. # Global variables: UncTable[] must be initialized to a lookup table by
  233. # MakeUncontrolTable() before using this function.
  234. function Uncontrol(S,  i,len,Output) {
  235.     len = length(S)
  236.     Output = ""
  237.     for (i = 1; i <= len; i++)
  238.     Output = Output UncTable[substr(S,i,1)]
  239.     return Output
  240. }
  241.  
  242. # MakeUncontrolTable: Make a table for use by Uncontrol().
  243. # Global variables:
  244. # UncTable[] is made into a character -> symbolic character lookup table.
  245. function MakeUncontrolTable(  i) {
  246.     for (i = 0; i < 32; i++)
  247.     UncTable[sprintf("%c",i)] = "^" sprintf("%c",i + 64)
  248.     for (i = 32; i < 127; i++)
  249.     UncTable[sprintf("%c",i)] = sprintf("%c",i)
  250.     UncTable[sprintf("%c",127)] = "^?"
  251.     for (i = 128; i < 256; i++)
  252.     UncTable[sprintf("%c",i)] = "\\" sprintf("%03o",i)
  253. }
  254. ' "$1"
  255. }
  256.  
  257. function GetInfo {
  258.     typeset file=$1 out ftype
  259.  
  260.     info=$(l -d -- "$file")
  261.     info="${info% $file}"
  262.  
  263.     ftype=$(file -- "$file")
  264.     ftype="${ftype##$file:*([     ])}"
  265.     print -r "File type: $ftype
  266. $info"
  267. }
  268.  
  269. # Usage: FixName filename printable-name
  270. function FixName {
  271.     typeset File=$1 PrintableName=$2
  272.     typeset info=$(GetInfo "$File")
  273.  
  274.     if $ListOnly; then
  275.     print -r -- "$PrintableName: $info $PrintableName"
  276.     return
  277.     fi
  278.     uSelect "File: $PrintableName
  279. $info $PrintableName" "" "Move#Move to:" Remove Skip && exit 0
  280.      case "$uSelect_ret" in
  281.      M)  [ -n "$uSelect_params" ] && mv -- "$file" "$uSelect_params";;
  282.      R)  if [ -d "$file" ]; then
  283.          rmdir -- "$file" ||  print -u2 \
  284. "$name: The directory could not be removed.  If the directory is not empty,
  285. you should instead use the 'm' option to rename it so that its contents can
  286. be dealt with."
  287.      else
  288.          rm -- "$file"
  289.      fi;;
  290.      esac
  291. }
  292.  
  293. # Usage: CheckName filename
  294. # Globals: foundBad
  295. function CheckName {
  296.     typeset file=$1
  297.  
  298.     $Debug && print -r -u2 "Checking file: $(Uncontrol "$file")"
  299.     if [ ! -a "$file" ]; then
  300.     print -r -u2 "$name: file does not exist: $(Uncontrol "$file")"
  301.     # All of the other characters have meaning to various shells.
  302.     # Check only the filename part of the path to be tested.
  303.     elif TestFilename "$file"; then
  304.     FixName "$file" "$(Uncontrol "$file")"
  305.     foundBad=true
  306.     fi
  307.     return 0
  308. }
  309.  
  310. # Globals: ExtraChars
  311. function TestFilename {
  312.     typeset file="${file##*/}"
  313.  
  314.     # Difficult to add metachars held in shell var to a pattern, since the
  315.     # statement must then be eval'ed, causing problems.  So, use awk to
  316.     # to do the test.
  317.     if [ -n "$ExtraChars" ]; then
  318.     awk '
  319. BEGIN {
  320.     Chars = ARGV[1]
  321.     Name = ARGV[2]
  322.     len = length(Name)
  323.     # Since the extra chars are liable to be metachars, avoid using them in
  324.     # any pattern match.  Instead, just remove them from the filename before
  325.     # comparing it to the legal name pattern.
  326.     Output = ""
  327.     for (i = 1; i <= len; i++)
  328.     if (!index(Chars,c = substr(Name,i,1)))
  329.         Output = Output c
  330.     if (Output !~ /^[-a-zA-Z0-9@%_+=:.,~#]*$/ || Output ~ /^[-~#]/)
  331.     exit 0
  332.     else
  333.     exit 1
  334. }
  335. ' "$ExtraChars" "$file"
  336.     else
  337.     [[ "$file" != +([-a-zA-Z0-9@%_+=:.,~#]) || "$file" = [-~#]* ]]
  338.     fi
  339. }
  340.  
  341. # @(#) ftw.ksh 1.0 95/07/30
  342. # 95/07/30 john h. dubois iii
  343. # Usage: ftw Path Command [optional-arg ...]
  344. # ftw will recursively search the directory tree rooted at Path, invoking
  345. # on each filename found, starting with Path itself:
  346. # Command optional-args Filename
  347. # where Command is the command name given, optional-args are any fixed args
  348. # given for Command, and Filename is the filename found.  ftw does not follow
  349. # symlinks.  If Path is not a directory, Command is invoked once, with that
  350. # filename.  ftw does a depth-first search, processing each directory before
  351. # the files in the directory.  ftw is liable to use a significant
  352. # amount of memory, causing the script interpreter to permanently grow in size.
  353. # ftw skips the . and .. entries in each directory, except for the Path given,
  354. # which will be processed even if it is . or ..
  355. # ftw will abort if Command ever exits nonzero.
  356. # Return value:
  357. # 0 on success.  1 if invoked incorrectly.
  358. # 2 if Command ever exits nonzero.
  359. function ftw {
  360.     set +o noglob    # Turn on globbing; local to this function
  361.     typeset Root=$1 file pat OIFS=$IFS IFS Command
  362.  
  363.     [ $# -lt 2 ] && return 1
  364.  
  365.     shift
  366.     set -A Command -- "$@"
  367.     "${Command[@]}" "$Root" || return 2
  368.     # ksh directory test returns true for symlink that points to dir, so must
  369.     # test for that everywhere that we test for dir
  370.     [ -L "$Root" -o ! -d "$Root" ] && return 0
  371.     for pat in "$Root/.[!.]*" "$Root/*"; do
  372.     IFS=
  373.     set -- $pat
  374.     IFS=$OIFS
  375.     [ $# = 1 -a "$1" = "$pat" -a ! -a "$1" ] && continue
  376.     for file; do
  377.         if [ ! -L "$file" -a -d "$file" ]; then
  378.         ftw "$file" "${Command[@]}" || return 2
  379.         else
  380.         "${Command[@]}" "$file" || return 2
  381.         fi
  382.     done
  383.     done
  384.     return 0
  385. }
  386.  
  387. ### Start of main program
  388.  
  389. unset Files[*]
  390. if $ReadInput; then
  391.     :
  392. elif $Recurse; then
  393.     [ $# -eq 0 ] && set -A Files . || set -A Files -- "$@"
  394. elif [ $# -eq 0 ]; then
  395.     # Find all files except . and ..
  396.     set -A Files -- .[!.]* *
  397.     typeset -i i=0 nf=${#Files[*]}
  398.     while [ i -lt nf ]; do
  399.     file=${Files[i]}
  400.     if [ ! -a "$file" ]; then
  401.         if [ "$file" = "*" -o "$file" = ".[!.]*" ]; then
  402.         unset Files[i]
  403.         elif [ -L "$file" ]; then
  404.         # If a filename that results from globbing does not exist
  405.         # and is a symlink, assume it is a bad symlink rather than
  406.         # a globbing failure, since bad symlinks are normal while
  407.         # globbing should never fail.
  408.         print -r -u2 \
  409.         "$name: Note: found bad symlink: $(Uncontrol "$file")"
  410.         unset Files[i]
  411.         else
  412.         print -r -u2 \
  413. "$name: globbing failure - got filename '$(Uncontrol "$file")'
  414. from directory listing, but it does not exist.  Continuing..."
  415.         fi
  416.     fi
  417.     let i+=1
  418.     done
  419.     if [ ${#Files[*]} -eq 0 ]; then
  420.     print -u2 "$name: No files found in current directory.  Exiting."
  421.     exit 1
  422.     fi
  423. else
  424.     if $ExpandDirs; then
  425.     # Expand directory names given on command line into a list of the files
  426.     # they contain.
  427.     for file in "$@"; do
  428.         if [ -d "$file" ]; then
  429.         set -- "$file"/*
  430.         # If no files in dir, skip it
  431.         [[ $# -eq 1 && "$1" = *"/*" && ! -a "$1" ]] && continue
  432.         set -A Files -- "${Files[@]}" "$@"
  433.         TestFilename "$file" && print -r -u2 \
  434. "$name: note: the directory name '$(Uncontrol "$file")' contains problem
  435. characters.  Use the -n option to act on directory names."
  436.         else
  437.         set -A Files -- "${Files[@]}" "$file"
  438.         fi
  439.     done
  440.     else
  441.     set -A Files -- "$@"
  442.     fi
  443. fi
  444.  
  445. set -o noglob
  446. foundBad=false
  447.  
  448. if $ReadInput; then
  449.     while read -r file; do
  450.     if $Recurse; then
  451.         ftw "$file" CheckName </dev/tty
  452.     else
  453.         CheckName "$file" </dev/tty
  454.     fi
  455.     done
  456. else
  457.     for file in "${Files[@]}"; do
  458.     if $Recurse; then
  459.         ftw "$file" CheckName
  460.     else
  461.         CheckName "$file"
  462.     fi
  463.     done
  464. fi
  465.  
  466. $foundBad || print -- "$name: No problem filenames found."
  467.