# SccsId[] = "%W% (USL function) %G%"
              PF_name="PRUNE_FILES"
              if [ ".${SECONDS}" = "." ]; then # Bourne function already loaded?
                 [ ."`set|egrep '^$PF_name\(\)\{$'`" != . ] && PF_loaded=1
              else # Korn or Bash shell and function already loaded?
                 if [ `expr "\`uname -s\`" : "[Ll][Ii][Nn][Uu][Xx]"` -eq 0 ]; then
                    [ ."`typeset +f|awk '/^'$PF_name'[=\(]?/'`" != . ] && PF_loaded=1
                 else # Linux
                    [ ."`typeset -F|awk '/^'$PF_name'[=\(]?/'`" != . ] && PF_loaded=1
                 fi
              fi
              if [ 0${PF_loaded} -eq 0 ]; then
              #----------------------------------------------------------------------#
              PRUNE_FILES() # Function documentation located at bottom.              #
              #----------------------------------------------------------------------#
              { [ ."${AWK}" = . ] && { { [ -x /usr/bin/nawk ] && AWK=/usr/bin/nawk; } \
                                  ||   { [ -x /bin/gawk     ] && AWK=/bin/gawk    ; } \
                                  ||   { [ -x /usr/bin/awk  ] && AWK=/usr/bin/awk ; }; }

                if [ .${SHLIB} = . ]; then SHLIB=/usr/local/scripts; export SHLIB; fi

                . $SHLIB/email_msg.sh # Calls $SHLIB/exit.sh

                #----------------------------------------------------------------#
                # If the following variables are not set, use these as defaults. #
                #----------------------------------------------------------------#
                : ${id_num=`id|sed 's/^\(uid=\)\([0-9]*\)\(.*\)/\2/'`}
                : ${id_hex=`echo "obase=16;$id_num"|bc`}
                : ${script_name:=`basename $0`}
                : ${name_root:=`echo $script_name|$AWK '{sub(/^\.+/,"");sub(/\..*/,"");print}'`}
                : ${tmp:=/var/tmp}
                : ${sp:="                    "}
                : ${yymmddhhmiss:=`date '+%y''%m%d%H''%M''%S'`}
                : ${Xtimestamp:=`echo "obase=16;$yymmddhhmiss+$$"|bc`}

                PF_ID="$script_name($PF_name)"

                if [ ."$log" = . ]; then
                   log=$name_root.log
                   [ ."$teelog" = . ] && teelog="cat"
                fi

                echo "`date '+%Y-%m-%d %T'` $PF_name $@" | $teelog

                #--------------------------------#
                # Parse this function's options. #
                #--------------------------------#
                PF_option=0
                PF_opt_s=0
                PF_opt_f=0
                PF_opt_l=0
                PF_opt_t=0
                MV_opt_S=0 # Use sudo for mv
                PF_opt_T=0 # Test run only
                PF_option=0
                PF_fld_len=6
                PF_sep_char="."
                PF_field_n="last"
                PF_follow=""

                PF_root=$tmp/$name_root"_PF_go_"$id_hex
                PF_err=$PF_root"."$Xtimestamp
                while getopts Ssf:l:Tt: PF_opt 2>> $PF_err
                do
                   case $PF_opt in
                     S ) PF_opt_S=1
                         PF_option=1
                         ;;
                     s ) PF_opt_s=1
                         PF_follow="-follow"
                         PF_option=1
                         ;;
                     f ) PF_opt_f=1
                         PF_field_n="$OPTARG"
                         PF_option=1
                         ;;
                     l ) PF_opt_l=1
                         PF_fld_len="$OPTARG"
                         PF_option=1
                         ;;
                     T ) PF_opt_T=1
                         PF_option=1
                         ;;
                     t ) PF_opt_t=1
                         PF_sep_char="$OPTARG"
                         PF_option=1
                         ;;
                    \? ) echo "$PF_ID" \
                           "Invalid option: -`sed 's/^.*-- //' $PF_err`" 1>&2
                         ;;
                     * ) ;;
                   esac
                done
                #--------------------------------------------#
                # Shift past options to remaining arguments. #
                #--------------------------------------------#
                [ $PF_option -ne 0 ] && shift `expr $OPTIND - 1`

                [ ."$PF_root" != . ] && \rm -f $PF_root* > /dev/null 2>&1

                #----------------------------------------------------------------#
                # Must reset this dog if this function is apt to be called again #
                OPTIND=1  # (try and find this fact documented anywhere else).   #
                #----------------------------------------------------------------#

                #----------------------------------------------------------#
                # Validate option and 'n' and 'file_root' arguments.       #
                # Nawk returns nonzero on failure or zero if all is well.  #
                #----------------------------------------------------------#
                PF_error_txt=`$AWK -v sp="$sp"             \
                                   -v PF_ID="$PF_ID"       \
                                   -v PF_name="$PF_name"   \
                                   -v opt_f=$PF_opt_f      \
                                   -v opt_l=$PF_opt_l      \
                                   -v opt_t=$PF_opt_t      \
                                   -v len=$PF_fld_len      \
                                   -v char=$PF_sep_char    \
                                   -v field=$PF_field_n    \
                  'BEGIN {
                           if (ARGV[2] == "")
                             exit_usage("Insufficient args.")
                           else if (! match(ARGV[1],/^[0-9]+$/))
                             exit_usage("Non-numeric keep files number ("ARGV[1]").")
                           else if (opt_l == 1 && (! match(len,/^[0-9]+$/)))
                             exit_usage("Non-numeric field length ("opt_l").\n")
                           else if (opt_t == 1 && char == "")
                           {
                             msg="Separator option (-t) supplied without its"
                             msg=msg"\n"sp"corresponding field separator character."
                             exit_usage(msg)
                           }
                           else if (! (field=="last"||match(field,/^[0-9]+$/)))
                             exit_usage("Non-numeric field number ("field").")
                           else if (field == 0)
                           {
                             msg="Invalid field number, "field
                             msg=msg" (must be greater than zero)."
                             exit_usage(msg)
                           }
                           exit 0 # Passed all the preceding tests
                         }
                         #----------------------------------------------------------#
                         function exit_usage (MSG)
                         #----------------------------------------------------------#
                         {
                           print sp""PF_ID": "MSG"\n"sp"Usage: "PF_name             ,
                             "-SsT -f field -l len -t char n file_root\n"           ,
                             "\n"sp,sp"-S = Use sudo to remove and compress files\n",
                             "\n"sp,sp"-s = Follow symbolic links.\n"               ,
                             "\n"sp,sp"-T = Test--neither remove nor compress files",
                             "\n"sp,sp"       --simply pretend we do.\n"            ,
                             "\n"sp,sp"-f = Test/sort field, \047field\047"         ,
                             "\n"sp,sp"       --default is the last field.\n"       ,
                             "\n"sp,sp"-l = Specifies the field length (6|8)"       ,
                             "\n"sp,sp"       --default is 6 (for yymmdd).\n"       ,
                             "\n"sp,sp"-t = Specifies separation character, char"   ,
                             "\n"sp,sp"       --default is a period.\n"             ,
                             "\n"sp,sp" n = Number of files we keep"                ,
                             "\n"sp,sp"       --remove files exceeding \047n\047.\n",
                             "\n"sp,sp"file_root = fully-qualified fileid"          ,
                             "\n"sp,sp"       --up to the numeric sort data"        ,
                             "\n"sp,sp"          (e.g. \047$HOME/vent/????.888\047" ,
                             "\n"sp,sp"             or \047$HOME/logs/daily_log\047)."
                           exit 1
                         }' "$1" "$2" 2>&1` # $1 is our target file_root argument.

                #----------------------------------------------------------------#
                # Test the returns status and fuss at the user if it is nonzero. #
                #----------------------------------------------------------------#
                if [ $? -ne 0 ]; then
                   EMAIL_MSG "ERROR: $PF_ID"           \
                     "$PF_error_txt"                   \
                     "${sp}Skipping prunes today ;-)." \
                     "${sp}$script_name ($$) continues."
                   return 1
                fi

                #--------------------------------------------------------#
                # Passed validation tests OK, now get about our business.#
                #--------------------------------------------------------#
                PF_last_n=$PF_field_n
                PF_keep_n=`expr $1 + 1`
                PF_dir=`dirname "$2"`
                [ $PF_dir = "." ] && PF_dir=`pwd`
                PF_file=`basename "$2"`

                #--------------------------------------------------------------#
                # Isolate the desired (possibly default) 6-digit numeric date  #
                # field in each line (sort_key) that follows $PF_sep_char and  #
                # use that field (as a sort key).  Prefix it to the line, then #
                # sort that new composite by that prefix (sort -inr -k 1,1).   #
                # Finally, use Awk to remove the prefix (sub(/^.* +/,"")) and  #
                # print the original (now sorted) lines.  Whew!                #
                #--------------------------------------------------------------#
                \find $PF_dir -type f -name "$PF_file*"      \
                  $PF_follow -print 2> /dev/null             \
                  | $AWK -F$PF_sep_char -v sp="$sp"          \
                                        -v dir=$PF_dir       \
                                        -v field=$PF_field_n \
                                        -v len=$PF_fld_len   \
                                        -v keep_n=$PF_keep_n \
                                        -v opt_S=$PF_opt_S   \
                                        -v opt_T=$PF_opt_T   \
                   'BEGIN \
                    { #-------------------------------------------------#
                      # Build sort key ([0-9]) that handles len digits. #
                      #-------------------------------------------------#
                      if (len == "") len = 6 # Assign default len if omitted

                      for (i = 1; i <= len; i++)
                        sort_key=sort_key"[0-9]"

                      #-------------------------------------------------#
                      # Initialize these before Action section.         #
                      #-------------------------------------------------#
                      i     = 0
                      err_n = 0

                      rm_cmd = "rm"   # Assign remove (rm) command
                      if (opt_S == 1) # Prefix with "sudo"?
                        rm_cmd = "sudo "rm_cmd

                      gzip="/usr/local/bin/gzip"
                      status=system("test -f "gzip)
                      if (status == 0)
                        cmp_prog = gzip
                      else
                      {
                        gzip="/usr/bin/gzip" # As of Solaris 2.9
                        status=system("test -f "gzip)
                        if (status == 0)
                          cmp_prog = gzip
                        else
                          cmp_prog = "compress"
                      }

                      if (opt_S == 1) cmp_prog = "sudo "cmp_prog # Prefix with "sudo "
                    } # E.O.BEGIN section

                    #---------------------------------------------------------------#
                    # Action section isolates the sort field, prefixing each line   #
                    # with that key, and assigning it to line[] array.  The END     #
                    # section sorts the array by that key and removes files (via    #
                    # "system" call) that exceed the number we want to keep.        #
                    #---------------------------------------------------------------#
                    {                                # Only until we find a line    #
                      if (template_found == 0)       # with a sort_key template,    #
                      {                              # and only if we sort the last #
                        if (field == "last")         # field, do we check every     #
                        {                            # period-delimited field (1st  #
                          for (n = 1; n <= NF; n++)  # to last) to get the last     #
                          {
                            if (match($n,sort_key))  # suitable field (prevents a   #
                            {
                              field=n                # ".(Z|gz)" from hosing up     #
                              template_found=1
                            }
                          }
                        }                            # the entire process).         #
                        else                         # Once we locate a file        #
                        {                            # matching our template, we    #
                          template_found=1           # continue processing.         #
                        }                            #------------------------------#
                      }

                      file_line = $0

                      #-----------------------------------------------------------#
                      # Following basename and dirname assignments and "sub()s"   #
                      # isolate those variables from each fileid.  The test       #
                      # following that, restricts processing to only those files  #
                      # found in the target directory (excludes subdirectories).  #
                      #-----------------------------------------------------------#
                      dirname = basename = $0
                      sub(/^.*\//,    "",basename)
                      sub("/"basename,"",dirname)
                      if (dirname == dir)
                      {
                        gsub(sort_key"*","((&))",$field)
                        gsub(/^.*\(++/, "",$field)  # Strip all before date field
                        gsub(/\)++.*$/, "",$field)  # and everything after it, then
                        $field=substr($field,1,len) # substring it to length.
                        if (! match($field,"^"sort_key"*$"))
                          err_files[++err_n] = file_line
                        else
                          line[++i]=$field" "file_line # Prefix line with sort key
                      }
                    } # End action section

                    END \
                    {
                      if (err_n > 0)
                      {
                        for (file in err_files)
                          print sp"Skipping "err_files[file] | "cat 1>&2"
                        print ""                             | "cat 1>&2"
                      }

                      if (template_found == 0)
                      {
                        print_err_msg()
                        exit 1
                      }

                      if (i == 0) exit

                      #-------------------------------------------------------#
                      # If we are really deleting/compressing files, we do    #
                      # not really have to sort them (since sorting is slow). #
                      # However, since "find" does not present its output in  #
                      # order, sorting does improve output readability.       #
                      # ----------------------------------------------------- #
                      # Sort line[] array (built in the preceding action      #
                      # section) in descending order by the first 6+ digits). #
                      #-------------------------------------------------------#
                      for (cursor = 1; cursor < i; cursor++)
                      {
                        lowest = cursor

                        for (n=cursor+1; n <= i ; n++)
                          if (substr(line[lowest],1,len) < substr(line[n],1,len))
                            lowest = n

                        #--------------------------------------------#
                        # Only if we find a lower value do we swap.  #
                        #--------------------------------------------#
                        if (lowest != cursor)
                        {
                          temp         = line[cursor]
                          line[cursor] = line[lowest]
                          line[lowest] = temp
                        }
                      } # for (cursor = 1 ; cursor < i ; cursor++)

                      if (i == 0) exit

                      #-------------------------------------------------------#
                      # Remove (or compress) every element in line[] array.   #
                      #-------------------------------------------------------#
                      (opt_S == 0) ? deleting="Deleting" : deleting="Deleting (via sudo)"

                      for (n=1 ; n <= i ; n++)
                      {
                        if (verbose) print sp""line[n]
                        sub(/^.* +/,"", line[n]) # Remove sort key,
                        #-----------------------------------------------------#
                        # Only if the dog exists, do we try to play with it.  #
                        #-----------------------------------------------------#
                        status=system("test -f "line[n])
                        if (status != 0) continue

                        if (n >= keep_n)
                        {
                          print sp""rm_cmd" "line[n]           # Inform the user,
                          if (! opt_T)                         # and if NOT a test,
                          {                                    # then go ahead and
                            status=system(rm_cmd" -f "line[n]) # remove the dog.
                            if (status != 0)
                            { #---------------------------------------#
                              # On error, fuss at the user and exit.  #
                              #---------------------------------------#
                              print sp"Delete ERROR "line[n]"!" | "cat 1>&2"
                              print sp"Delete ERROR "line[n]"!"
                              exit 1
                            }
                          }
                        }
                        else if (n > 1 && (! match(line[n],/\.(Z|gz)$/)))
                        {
                          print sp""cmp_prog" "line[n]           # Inform the user
                          if (! opt_T)                           # and if NOT a test,
                          {                                      # then go ahead and
                            status=system(cmp_prog" -f "line[n]) # compress the dog.
                            if (status == 1)
                            { #---------------------------------------#
                              # On error, fuss at the user and exit.  #
                              #---------------------------------------#
                              print sp""cmp_prog" ERROR "line[n]"!" | "cat 1>&2"
                              print sp""cmp_prog" ERROR "line[n]"!"
                              exit 1
                            }
                          }
                        }
                      }
                    } # E.O.END section
                    #-------------------------------------------------------#
                    function print_err_msg() # Globals: len
                    #-------------------------------------------------------#
                    {
                      print sp"Nothing deleted. No "len"-digit numeric key",
                            "in sort target fileid!",
                            "\n"sp"Use \047-f\047 option to specify a different",
                            "sort field",
                            "\n"sp"or \047-l\047 option to specify a different",
                            "sort field length." | "cat 1>&2"
                    }' | $teelog
                PF_status=$?

                if [ $PF_status -ne 0 ]; then
                   EMAIL_MSG "ERROR: $PF_ID"                        \
                     "${sp}Problem pruning $2.  Check out the log." \
                     "${sp}Skipping prunes today ;-)."              \
                     "${sp}$script_name ($$) continues."
                fi

                return $PF_status
              } # "PF_" prefix identifies this function's variables.
              fi

              #======================================================================#
              #                       D O C U M E N T A T I O N                      #
              #======================================================================#
              #                                                                      #
              #      Author: Bob Orlando                                             #
              #                                                                      #
              #        Date: April 2, 1997                                           #
              #                                                                      #
              #  Program ID: prune_files.sh                                          #
              #                                                                      #
              #       Usage: PRUNE_FILES -SsT -f field -l 6|8 -T -t char n file_root #
              #                                                                      #
              #                          -S = Use sudo to remove/compress files.     #
              #                                                                      #
              #                          -s = Follow symbolic links.                 #
              #                                                                      #
              #                          -T = Test: neither remove nor compress      #
              #                                 files--simply pretend we do.         #
              #                                                                      #
              #                          -f = sort field, 'field'                    #
              #                                 -- default is the last field.        #
              #                                                                      #
              #                          -l = specifies the sort field length, len   #
              #                                 -- default is 6 (for yymmdd).        #
              #                                                                      #
              #                          -t = specifies separation character, char   #
              #                                 -- default is a period.              #
              #                                                                      #
              #                           n = number of files we keep                #
              #                                 -- remove files exceeding 'n'.       #
              #                                                                      #
              #                    file_root = fully-qualified fileid                #
              #                                  -- up to the numeric sort data      #
              #                                     (e.g. "$HOME/vent/????.888"      #
              #                                        or "$HOME/logs/daily_log").   #
              #                                                                      #
              #     Purpose: Prune files with [yy]yymmdd in the fileid to a given    #
              #              number.  Additionally, all files, except for the        #
              #              latest file, are also compressed using gzip (first      #
              #              choice) or compress (last choice).                      #
              #                                                                      #
              # Description: This function requires that the files being pruned have #
              #              either yymmdd or yyyymmdd date string in their fileids. #
              #              This is critical because this program deletes these     #
              #              files by sorting the most recent files to the top of    #
              #              our list and removing those from the end that exceed    #
              #              the number of files we want to retain.                  #
              #                                                                      #
              #              The ability to specify a field on which to key is       #
              #              useful because files may have other numeric strings     #
              #              in their fileids, or they may have multiple date        #
              #              strings (e.g. a "data" date and a process date --       #
              #              'deletes.000101_000401' -- data dated January 1,        #
              #              but processed/created on April 1).                      #
              #                                                                      #
              #        NOTE: This process ASSUMES that there are NOT duplicate       #
              #              values in the sort field which must be retained (e.g.   #
              #              multiple files with the same dates).  If you wish to    #
              #              "remove" files by date (yymmdd) and preservation by     #
              #              date is required, then check out the REMOVE_BY_DATE     #
              #              library function (it does not compress files).          #
              #                                                                      #
              #    Examples: Assume the following files (containing two periods and  #
              #              two underscores which result in three fields each).     #
              #                                                                      #
              #                     +----------+------------ Underscores             #
              #                     |      +-- | -----+----- Periods                 #
              #                     |      |   |      |                              #
              #                     v      v   v      v                              #
              #                 test_000101.out_000120.10                            #
              #                 test_000102.out_000119.11                            #
              #                 test_000103.out_000118.12                            #
              #                 test_000104.out_000117.13                            #
              #                 test_000105.out_000116.14                            #
              #                 test_000106.out_000115.15                            #
              #                 test_000107.out_000114.16                            #
              #                 test_000108.out_000113.17                            #
              #                 test_000109.out_000112.18                            #
              #                 test_000110.out_000111.19                            #
              #                                                                      #
              #                 Using the first line, our period-delimited fields    #
              #                 are:                                                 #
              #                       f1 = test_000101                               #
              #                       f2 = out_000120                                #
              #                       f3 = 10      -- has no 6-digit (date) string.  #
              #                                                                      #
              #                 Using the first line, unserscore-delimited fields    #
              #                 are:                                                 #
              #                       f1 = test    -- has no 6-digit (date) string.  #
              #                       f2 = 000101.out                                #
              #                       f3 = 000120.10                                 #
              #                                                                      #
              #              1. Prunes files based on the last date-like (6-digit)   #
              #                 period-delimited field.  Example 'a' uses both       #
              #                 defaults.  Example 'b' uses '-f2' (which is the last #
              #                 period-delimited field containing a datelike numeric #
              #                 string).  (Keeping 7 files.)                         #
              #                                                                      #
              #                 a. PRUNE_FILES         7 test_00                     #
              #                 b. PRUNE_FILES -t. -f2 7 test_00                     #
              #                                                                      #
              #                    Deleting test_000108.out_000113.17                #
              #                    Deleting test_000109.out_000112.18                #
              #                    Deleting test_000110.out_000111.19                #
              #                                                                      #
              #              2. Prunes files based on the last, 2-digit, period--    #
              #                 delimited field.                                     #
              #                                                                      #
              #                    PRUNE_FILES -l2 7 test_00                         #
              #                                                                      #
              #                    Deleting test_000103.out_000118.12                #
              #                    Deleting test_000102.out_000119.11                #
              #                    Deleting test_000101.out_000120.10                #
              #                                                                      #
              #              3. Prunes files based on the first date-like (8-digit)  #
              #                 period-delimited field. Example 'a' uses the default #
              #                 field delimiter (.), but requests first field (-f1). #
              #                 Example 'b' is functionally identical.               #
              #                                                                      #
              #                 a. PRUNE_FILES     -f1 5 test_00                     #
              #                 b. PRUNE_FILES -t. -f1 5 test_00                     #
              #                                                                      #
              #                    Deleting test_000105.out_000116.14                #
              #                    Deleting test_000104.out_000117.13                #
              #                    Deleting test_000103.out_000118.12                #
              #                    Deleting test_000102.out_000119.11                #
              #                    Deleting test_000101.out_000120.10                #
              #                                                                      #
              #              4. Prunes files based on the first date-like (6-digit)  #
              #                 underscore-delimited field.  Example uses '-t_' to   #
              #                 specify an underscore as the field delimiter (it can #
              #                 be any character).  The last field is the default.   #
              #                                                                      #
              #                 PRUNE_FILES -t_ 8 test_00                            #
              #                                                                      #
              #                    Deleting test_000109.out_000112.18                #
              #                    Deleting test_000110.out_000111.19                #
              #                                                                      #
              #              5. This example shows a common error as it attempts     #
              #                 to prune files from the first underscore-delimited   #
              #                 field.  (The 'test_' portion of the line is the      #
              #                 the first underscore-delimited field.  The user      #
              #                 should have specified -f2 to isolate the date        #
              #                 following 'test_' and before '.out_00...'.)          #
              #                 Since the first field has no date string, the        #
              #                 following error message is printed.                  #
              #                                                                      #
              #                 PRUNE_FILES -t_ -f1 5 test_00                        #
              #                                                                      #
              #                    No date string in field 1!                        #
              #                    Nothing deleted.                                  #
              #                                                                      #
              #     Globals: No global variables assigned from this function.        #
              #              "PF_" prefix identifies local function variables.       #
              #                                                                      #
              #       Calls: EMAIL_MSG and EXIT library functions.                   #
              #                                                                      #
              # Exit_status: 0 = Success                                             #
              #              1 = Any failure                                         #
              #                                                                      #
              #       Notes: ......................................................  #
              #              ......................................................  #
              #                                                                      #
              #    Modified: 2004-08-05 Bob Orlando                                  #
              #                 v1.19 * Add sudo (-S) and test (-T) options.         #
              #                         (Test option neither removes nor compresses  #
              #                         files, just says what it WOULD remove or     #
              #                         compress).                                   #
              #                                                                      #
              #----------------------------------------------------------------------#
            
Artificial Intelligence is no match for natural stupidity.
©Copyright Bob Orlando, 1997-2011
All rights reserved.
http://www.OrlandoKuntao.com
E-mail: Bob@OrlandoKuntao.com
Last update: Jan. 26, 2011
by Bob Orlando