home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / fixdate < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  30.5 KB  |  837 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) fixdate.gawk 1.0 93/09/26
  3. # 93/09/26 john h. dubois iii (john@armory.com)
  4. #
  5. # Use gawk for strftime(), /dev/stderr, and because it recognizes changes
  6. # to ARGV/ARGC
  7.  
  8. BEGIN {
  9.     Usage = \
  10. "Usage: fixdate [-chiIuz] [-d datesep] [-r recsep]\n"\
  11. "               [-t fieldsep] [-y year] field-num [file ...]"
  12.     ARGC = Opts("fixdate",Usage,"chiIuzd:r:t:y:",1)
  13.     if ("h" in Options) {
  14.     print \
  15. "fixdate: convert date fields from month & day only to fully specified form.\n"\
  16. Usage "\n"\
  17. "The specified field of each record is parsed as a date and rewritten to\n"\
  18. "include leading zeros in each field, and to include a year if none given.\n"\
  19. "Date components must be numeric.\n"\
  20. "The modified records are written to the standard output.\n"\
  21. "-c: Include the century in the year.  The default is to use two digits.\n"\
  22. "    A century part is also added to dates that include a year but not a\n"\
  23. "    century.  If -y is given, its century part will be used for the\n"\
  24. "    expansion; if not, the current century is used.\n"\
  25. "-d: Set the date component separator.  The default is forward slash (/).\n"\
  26. "-i: If the specified field does not look like it was intended as a date,\n"\
  27. "    the record is printed without modification.  The default is to print\n"\
  28. "    an error message and discard the record.\n"\
  29. "-I: Like -i, except fields that look like dates are also printed unmodified\n"\
  30. "    if there is a problem with them.  An warning message is still printed.\n"\
  31. "-r: Set the record separator.  The default is newline.\n"\
  32. "-t: Set the field separator.  The default is tab.  The separator can be a\n"\
  33. "    string, but may not contain any regular expression metacharacters.\n"\
  34. "-u: Use US date format (month/day/year).  The default is to use sorting\n"\
  35. "    format (year/month/date).\n"\
  36. "-y: Set the year added to dates.  The default is to use the current year.\n"\
  37. "-z: Add leading zeros only; dates that do not contain a year are in error."
  38.     exit(0)
  39.     }
  40.     if ("u" in Options)
  41.     YearField = 3
  42.     else
  43.     YearField = 1
  44.     ZeroOnly = "z" in Options
  45.     Century = "c" in Options
  46.     IgnoreErr = "i" in Options || "I" in Options
  47.     IgnoreBadDate = "I" in Options
  48.     if ("d" in Options)
  49.     DateSep = Options["d"]
  50.     else
  51.     DateSep = "/"
  52.     if ("t" in Options)
  53.     FS = OFS = Options["t"]
  54.     else
  55.     FS = OFS = "\t"
  56.     if ("r" in Options)
  57.     RS = ORS = Options["r"]
  58.     if ("y" in Options) {
  59.     ExpYear = Options["y"]
  60.     if ((ExpYear+0) < 100)
  61.         ExpYear += int(strftime("%Y")/100) * 100
  62.     }
  63.     else
  64.     ExpYear = strftime("%Y")
  65.     if (Century) {
  66.     ExpCentury = int(ExpYear / 100) * 100
  67.     YearDig = 4
  68.     }
  69.     else {
  70.     ExpYear %= 100
  71.     YearDig = 2
  72.     }
  73.     DateField = ARGV[1]
  74.     delete ARGV[1]
  75. }
  76.  
  77. {
  78.     if (NF < DateField) {
  79.     if (IgnoreErr)
  80.         print $0
  81.     else
  82.         FileErr("Not enough fields in record")
  83.     next
  84.     }
  85.     if ((GoodDate = MakeGoodDate($DateField,DateSep)) ~ /^.$/) {
  86.     if (IgnoreErr && GoodDate == 1)
  87.         print $0
  88.     else {
  89.         FileErr(ERRNAME)
  90.         if (IgnoreBadDate)
  91.         print $0
  92.         print "Record printed unmodifed." > "/dev/stderr"
  93.     }
  94.     next
  95.     }
  96.     $DateField = GoodDate
  97.     print $0
  98. }
  99.  
  100. # Returns 1 if no date separators found in field.
  101. # Returns 2 if bad values found in date fields.
  102. # Otherwise returns fixed up InDate.
  103. function MakeGoodDate(InDate,DateSep,  DateElem,NumElem,Month,Day,Year) {
  104.     if (InDate ~ /^[ \t]*$/) {
  105.     ERRNAME = "Empty date field"
  106.     return 1
  107.     }
  108.     if ((NumElem = split(InDate,DateElem,DateSep)) < 2) {
  109.     ERRNAME = "Need at least two fields in date"
  110.     return 1
  111.     }
  112.     if (NumElem > 3) {
  113.     ERRNAME = "Too many fields in date"
  114.     return 2
  115.     }
  116.     if (ZeroOnly && NumElem < 3) {
  117.     ERRNAME = "No year given in date."
  118.     return 2
  119.     }
  120.     for (i = 1; i <= NumElem; i++)
  121.     if (DateElem[i] !~ /^[0-9]+$/ || DateElem[i] + 0 < 1) {
  122.         ERRNAME = "Bad field in date"
  123.         return 2
  124.     }
  125.     if (NumElem == 2 || YearField == 3) {
  126.     Month = DateElem[1]
  127.     Day = DateElem[2]
  128.     }
  129.     else {
  130.     Month = DateElem[2]
  131.     Day = DateElem[3]
  132.     }
  133.     if (NumElem == 3)
  134.     Year = DateElem[YearField]
  135.     else 
  136.     Year = ExpYear
  137.     if (Year < 100 && Century)
  138.     Year += ExpCentury
  139.     # * in format doesn't work
  140.     if (YearField == 3)
  141.     return \
  142.     sprintf("%02d%s%02d%s%" YearDig "d",Month,DateSep,Day,DateSep,Year)
  143.     else
  144.     return \
  145.     sprintf("%" YearDig "d%s%02d%s%02d",Year,DateSep,Month,DateSep,Day)
  146. }
  147.  
  148. ### Start of ProcArgs library
  149. # @(#) ProcArgs 1.11 96/12/08
  150. # 92/02/29 john h. dubois iii (john@armory.com)
  151. # 93/07/18 Added "#" arg type
  152. # 93/09/26 Do not count -h against MinArgs
  153. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  154. #          Removed meaning of "+" or "-" by itself.
  155. # 94/03/08 Added & option and *()< option types.
  156. # 94/04/02 Added NoRCopt to Opts()
  157. # 94/06/11 Mark numeric variables as such.
  158. # 94/07/08 Opts(): Do not require any args if h option is given.
  159. # 95/01/22 Record options given more than once.  Record option num in argv.
  160. # 95/06/08 Added ExclusiveOptions().
  161. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  162. #          Expand $VARNAME at the start of its filenames.
  163. #          Let varname=0 and -option- turn off an option.
  164. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  165. #          of the vars should be searched for in the environment.
  166. #          Check for duplicate rcfiles.
  167. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  168. #          now return various negatives values on error, not just -1, and
  169. #          Opts() may set Err to various positive values, not just 1.
  170. #          Added AllowUnrecOpt.
  171. # 96/05/23 Check type given for & option
  172. # 96/06/15 Re-port to awk
  173. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  174. #          used by other functions.
  175. # 96/10/15 Added OptChars
  176. # 96/11/01 Added exOpts arg to Opts()
  177. # 96/11/16 Added ; type
  178. # 96/12/08 Added Opt2Set() & Opt2Sets()
  179. # 96/12/27 Added CmdLineOpt()
  180.  
  181. # optlist is a string which contains all of the possible command line options.
  182. # A character followed by certain characters indicates that the option takes
  183. # an argument, with type as follows:
  184. # :    String argument
  185. # ;    Non-empty string argument
  186. # *    Floating point argument
  187. # (    Non-negative floating point argument
  188. # )    Positive floating point argument
  189. # #    Integer argument
  190. # <    Non-negative integer argument
  191. # >    Positive integer argument
  192. # The only difference the type of argument makes is in the runtime argument
  193. # error checking that is done.
  194.  
  195. # The & option is a special case used to get numeric options without the
  196. # user having to give an option character.  It is shorthand for [-+.0-9].
  197. # If & is included in optlist and an option string that begins with one of
  198. # these characters is seen, the value given to "&" will include the first
  199. # char of the option.  & must be followed by a type character other than ":"
  200. # or ";".
  201. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  202.  
  203. # Strings in argv[] which begin with "-" or "+" are taken to be
  204. # strings of options, except that a string which consists solely of "-"
  205. # or "+" is taken to be a non-option string; like other non-option strings,
  206. # it stops the scanning of argv and is left in argv[].
  207. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  208. # If an option takes an argument, the argument may either immediately
  209. # follow it or be given separately.
  210. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  211. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  212. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  213. # this feature had a flaw that caused problems in some cases.  See the OptChars
  214. # parameter to explicitly set the option-specifier characters.
  215.  
  216. # If an option that does not take an argument is given,
  217. # an index with its name is created in Options and its value is set to the
  218. # number of times it occurs in argv[].
  219.  
  220. # If an option that does take an argument is given, an index with its name is
  221. # created in Options and its value is set to the value of the argument given
  222. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  223. # If an option that takes an argument is given more than once,
  224. # Options[option-name,"count"] is incremented, and the value is assigned to
  225. # the index (option-name,instance) where instance is 2 for the second occurance
  226. # of the option, etc.
  227. # In other words, the first time an option with a value is encountered, the
  228. # value is assigned to an index consisting only of its name; for any further
  229. # occurances of the option, the value index has an extra (count) dimension.
  230.  
  231. # The sequence number for each option found in argv[] is stored in
  232. # Options[option-name,"num",instance], where instance is 1 for the first
  233. # occurance of the option, etc.  The sequence number starts at 1 and is
  234. # incremented for each option, both those that have a value and those that
  235. # do not.  Options set from a config file have a value of 0 assigned to this.
  236.  
  237. # Options and their arguments are deleted from argv.
  238. # Note that this means that there may be gaps left in the indices of argv[].
  239. # If compress is nonzero, argv[] is packed by moving its elements so that
  240. # they have contiguous integer indices starting with 0.
  241. # Option processing will stop with the first unrecognized option, just as
  242. # though -- was given except that unlike -- the unrecognized option will not be
  243. # removed from ARGV[].  Normally, an error value is returned in this case.
  244. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  245. # be found, so the number of remaining arguments is returned instead.
  246. # If OptChars is not a null string, it is the set of characters that indicate
  247. # that an argument is an option string if the string begins with one of the
  248. # characters.  A string consisting solely of two of the same option-indicator
  249. # characters stops the scanning of argv[].  The default is "-+".
  250. # argv[0] is not examined.
  251. # The number of arguments left in argc is returned.
  252. # If an error occurs, the global string OptErr is set to an error message
  253. # and a negative value is returned.
  254. # Current error values:
  255. # -1: option that required an argument did not get it.
  256. # -2: argument of incorrect type supplied for an option.
  257. # -3: unrecognized (invalid) option.
  258. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  259. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  260. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  261. {
  262. # ArgNum is the index of the argument being processed.
  263. # ArgsLeft is the number of arguments left in argv.
  264. # Arg is the argument being processed.
  265. # ArgLen is the length of the argument being processed.
  266. # ArgInd is the position of the character in Arg being processed.
  267. # Option is the character in Arg being processed.
  268. # Pos is the position in OptList of the option being processed.
  269. # NumOpt is true if a numeric option may be given.
  270.     ArgsLeft = argc
  271.     NumOpt = index(OptList,"&")
  272.     OptionNum = 0
  273.     if (OptChars == "")
  274.     OptChars = "-+"
  275.     while (OptChars != "") {
  276.     c = substr(OptChars,1,1)
  277.     OptChars = substr(OptChars,2)
  278.     OptCharSet[c]
  279.     OptTerm[c c]
  280.     }
  281.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  282.     Arg = argv[ArgNum]
  283.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  284.         break    # Not an option; quit
  285.     if (Arg in OptTerm) {
  286.         delete argv[ArgNum]
  287.         ArgsLeft--
  288.         break
  289.     }
  290.     ArgLen = length(Arg)
  291.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  292.         Option = substr(Arg,ArgInd,1)
  293.         if (NumOpt && Option ~ /[-+.0-9]/) {
  294.         # If this option is a numeric option, make its flag be & and
  295.         # its option string flag position be the position of & in
  296.         # the option string.
  297.         Option = "&"
  298.         Pos = NumOpt
  299.         # Prefix Arg with a char so that ArgInd will point to the
  300.         # first char of the numeric option.
  301.         Arg = "&" Arg
  302.         ArgLen++
  303.         }
  304.         # Find position of flag in option string, to get its type (if any).
  305.         # Disallow & as literal flag.
  306.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  307.         if (AllowUnrecOpt) {
  308.             Escape = 1
  309.             break
  310.         }
  311.         else {
  312.             OptErr = "Invalid option: " specGiven Option
  313.             return -3
  314.         }
  315.         }
  316.  
  317.         # Find what the value of the option will be if it takes one.
  318.         # NeedNextOpt is true if the option specifier is the last char of
  319.         # this arg, which means that if the option requires a value it is
  320.         # the next arg.
  321.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  322.         if (GotValue = ArgNum + 1 < argc)
  323.             Value = argv[ArgNum+1]
  324.         }
  325.         else {    # Value is included with option
  326.         Value = substr(Arg,ArgInd + 1)
  327.         GotValue = 1
  328.         }
  329.  
  330.         if (HadValue = AssignVal(Option,Value,Options,
  331.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  332.         specGiven)) {
  333.         if (HadValue < 0)    # error occured
  334.             return HadValue
  335.         if (HadValue == 2)
  336.             ArgInd++    # Account for the single-char value we used.
  337.         else {
  338.             if (NeedNextOpt) {    # option took next arg as value
  339.             delete argv[++ArgNum]
  340.             ArgsLeft--
  341.             }
  342.             break    # This option has been used up
  343.         }
  344.         }
  345.     }
  346.     if (Escape)
  347.         break
  348.     # Do not delete arg until after processing of it, so that if it is not
  349.     # recognized it can be left in ARGV[].
  350.     delete argv[ArgNum]
  351.     ArgsLeft--
  352.     }
  353.     if (compress != 0) {
  354.     dest = 1
  355.     src = argc - ArgsLeft + 1
  356.     for (count = ArgsLeft - 1; count; count--) {
  357.         ARGV[dest] = ARGV[src]
  358.         dest++
  359.         src++
  360.     }
  361.     }
  362.     return ArgsLeft
  363. }
  364.  
  365. # Assignment to values in Options[] occurs only in this function.
  366. # Option: Option specifier character.
  367. # Value: Value to be assigned to option, if it takes a value.
  368. # Options[]: Options array to return values in.
  369. # ArgType: Argument type specifier character.
  370. # GotValue: Whether any value is available to be assigned to this option.
  371. # Name: Name of option being processed.
  372. # OptionNum: Number of this option (starting with 1) if set in argv[],
  373. #     or 0 if it was given in a config file or in the environment.
  374. # SingleOpt: true if the value (if any) that is available for this option was
  375. #     given as part of the same command line arg as the option.  Used only for
  376. #     options from the command line.
  377. # specGiven is the option specifier character use, if any (e.g. - or +),
  378. # for use in error messages.
  379. # Global variables: OptErr
  380. # Return value: negative value on error, 0 if option did not require an
  381. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  382. # the arg.
  383. # Current error values:
  384. # -1: Option that required an argument did not get it.
  385. # -2: Value of incorrect type supplied for option.
  386. # -3: Bad type given for option &
  387. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  388. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  389.     # If option takes a value...    [
  390.     NumTypes = "*()#<>]"
  391.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  392.     OptErr = "Bad type given for & option"
  393.     return -3
  394.     }
  395.  
  396.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  397.     if (!GotValue) {
  398.         if (Name != "")
  399.         OptErr = "Variable requires a value -- " Name
  400.         else
  401.         OptErr = "option requires an argument -- " Option
  402.         return -1
  403.     }
  404.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  405.         OptErr = Err
  406.         return -2
  407.     }
  408.     # Mark this as a numeric variable; will be propogated to Options[] val.
  409.     if (ArgType != ":" && ArgType != ";")
  410.         Value += 0
  411.     if ((Instance = ++Options[Option,"count"]) > 1)
  412.         Options[Option,Instance] = Value
  413.     else
  414.         Options[Option] = Value
  415.     }
  416.     # If this is an environ or rcfile assignment & it was given a value...
  417.     else if (!OptionNum && Value != "") {
  418.     UsedValue = 1
  419.     # If the value is "0" or "-" and this is the first instance of it,
  420.     # do not set Options[Option]; this allows an assignment in an rcfile to
  421.     # turn off an option (for the simple "Option in Options" test) in such
  422.     # a way that it cannot be turned on in a later file.
  423.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  424.         Instance = 1
  425.     else
  426.         Instance = ++Options[Option]
  427.     # Save the value even though this is a flag
  428.     Options[Option,Instance] = Value
  429.     }
  430.     # If this is a command line flag and has a - following it in the same arg,
  431.     # it is being turned off.
  432.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  433.     UsedValue = 2
  434.     if (Option in Options)
  435.         Instance = ++Options[Option]
  436.     else
  437.         Instance = 1
  438.     Options[Option,Instance]
  439.     }
  440.     # If this is a flag assignment without a value, increment the count for the
  441.     # flag unless it was turned off.  The indicator for a flag being turned off
  442.     # is that the flag index has not been set in Options[] but it has an
  443.     # instance count.
  444.     else if (Option in Options || !((Option,1) in Options))
  445.     # Increment number of times this flag seen; will inc null value to 1
  446.     Instance = ++Options[Option]
  447.     Options[Option,"num",Instance] = OptionNum
  448.     return UsedValue
  449. }
  450.  
  451. # Option is the option letter
  452. # Value is the value being assigned
  453. # Name is the var name of the option, if any
  454. # ArgType is one of:
  455. # :    String argument
  456. # ;    Non-null string argument
  457. # *    Floating point argument
  458. # (    Non-negative floating point argument
  459. # )    Positive floating point argument
  460. # #    Integer argument
  461. # <    Non-negative integer argument
  462. # >    Positive integer argument
  463. # specGiven is the option specifier character use, if any (e.g. - or +),
  464. # for use in error messages.
  465. # Returns null on success, err string on error
  466. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  467.     if (ArgType == ":")
  468.     return ""
  469.     if (ArgType == ";") {
  470.     if (Value == "")
  471.         Err = "must be a non-empty string"
  472.     }
  473.     # A number begins with optional + or -, and is followed by a string of
  474.     # digits or a decimal with digits before it, after it, or both
  475.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  476.     Err = "must be a number"
  477.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  478.     Err = "may not include a fraction"
  479.     else if (ArgType ~ "[()<>]" && Value < 0)
  480.     Err = "may not be negative"
  481.     # (
  482.     else if (ArgType ~ "[)>]" && Value == 0)
  483.     Err = "must be a positive number"
  484.     if (Err != "") {
  485.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  486.     if (Name != "")
  487.         return ErrStr "variable " substr(Name,1,1) " " Err
  488.     else {
  489.         if (Option == "&")
  490.         Option = Value
  491.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  492.     }
  493.     }
  494.     else
  495.     return ""
  496. }
  497.  
  498. # Note: only the above functions are needed by ProcArgs.
  499. # The rest of these functions call ProcArgs() and also do other
  500. # option-processing stuff.
  501.  
  502. # Opts: Process command line arguments.
  503. # Opts processes command line arguments using ProcArgs()
  504. # and checks for errors.  If an error occurs, a message is printed
  505. # and the program is exited.
  506. #
  507. # Input variables:
  508. # Name is the name of the program, for error messages.
  509. # Usage is a usage message, for error messages.
  510. # OptList the option description string, as used by ProcArgs().
  511. # MinArgs is the minimum number of non-option arguments that this
  512. # program should have, non including ARGV[0] and +h.
  513. # If the program does not require any non-option arguments,
  514. # MinArgs should be omitted or given as 0.
  515. # rcFiles, if given, is a colon-seprated list of filenames to read for
  516. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  517. # by the value of the environment variable HOME.  If a filename begins with
  518. # $, the part from the character after the $ up until (but not including)
  519. # the first character not in [a-zA-Z0-9_] will be searched for in the
  520. # environment; if found its value will be substituted, if not the filename will
  521. # be discarded.
  522. # rcfiles are read in the order given.
  523. # Values given in them will not override values given on the command line,
  524. # and values given in later files will not override those set in earlier
  525. # files, because AssignVal() will store each with a different instance index.
  526. # The first instance of each variable, either on the command line or in an
  527. # rcfile, will be stored with no instance index, and this is the value
  528. # normally used by programs that call this function.
  529. # VarNames is a comma-separated list of variable names to map to options,
  530. # in the same order as the options are given in OptList.
  531. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  532. # searched for in the environment.  If set to -1, all values will be searched
  533. # for in the environment.  Values given in the environment will override
  534. # those given in the rcfiles but not those given on the command line.
  535. # NoRCopt, if given, is an additional letter option that if given on the
  536. # command line prevents the rcfiles from being read.
  537. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  538. # ExclusiveOptions() for a description of exOpts.
  539. # Special options:
  540. # If x is made an option and is given, some debugging info is output.
  541. # h is assumed to be the help option.
  542.  
  543. # Global variables:
  544. # The command line arguments are taken from ARGV[].
  545. # The arguments that are option specifiers and values are removed from
  546. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  547. # The number of elements in ARGV[] should be in ARGC.
  548. # After processing, ARGC is set to the number of elements left in ARGV[].
  549. # The option values are put in Options[].
  550. # On error, Err is set to a positive integer value so it can be checked for in
  551. # an END block.
  552. # Return value: The number of elements left in ARGV is returned.
  553. # Must keep OptErr global since it may be set by InitOpts().
  554. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  555. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  556.     if (MinArgs == "")
  557.     MinArgs = 0
  558.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  559.     optChars)
  560.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  561.     if (ArgsLeft >= 0) {
  562.         OptErr = "Not enough arguments"
  563.         Err = 4
  564.     }
  565.     else
  566.         Err = -ArgsLeft
  567.     printf "%s: %s.\nUse -h for help.\n%s\n",
  568.     Name,OptErr,Usage > "/dev/stderr"
  569.     exit 1
  570.     }
  571.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  572.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  573.     {
  574.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  575.     Err = -e
  576.     exit 1
  577.     }
  578.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  579.     {
  580.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  581.     Err = 1
  582.     exit 1
  583.     }
  584.     return ArgsLeft
  585. }
  586.  
  587. # ReadConfFile(): Read a file containing var/value assignments, in the form
  588. # <variable-name><assignment-char><value>.
  589. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  590. # line and whitespace between the variable name and the assignment character) 
  591. # is stripped.  Lines that do not contain an assignment operator or which
  592. # contain a null variable name are ignored, other than possibly being noted in
  593. # the return value.  If more than one assignment is made to a variable, the
  594. # first assignment is used.
  595. # Input variables:
  596. # File is the file to read.
  597. # Comment is the line-comment character.  If it is found as the first non-
  598. #     whitespace character on a line, the line is ignored.
  599. # Assign is the assignment string.  The first instance of Assign on a line
  600. #     separates the variable name from its value.
  601. # If StripWhite is true, whitespace around the value (whitespace between the
  602. #     assignment char and trailing whitespace on the line) is stripped.
  603. # VarPat is a pattern that variable names must match.  
  604. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  605. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  606. #     a line; no assignment operator is needed.  These variables are set in
  607. #     the output array with a null value.  Lines containing nothing but
  608. #     whitespace are still ignored.
  609. # Output variables:
  610. # Values[] contains the assignments, with the indexes being the variable names
  611. #     and the values being the assigned values.
  612. # Lines[] contains the line number that each variable occured on.  A flag set
  613. #     is record by giving it an index in Lines[] but not in Values[].
  614. # Return value:
  615. # If any errors occur, a string consisting of descriptions of the errors
  616. # separated by newlines is returned.  In no case will the string start with a
  617. # numeric value.  If no errors occur,  the number of lines read is returned.
  618. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  619. FlagsOK,
  620. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  621.     if (Comment != "")
  622.     Comment = "^" Comment
  623.     AssignLen = length(Assign)
  624.     if (VarPat == "")
  625.     VarPat = "."    # null varname not allowed
  626.     while ((Status = (getline Line < File)) == 1) {
  627.     LineNum++
  628.     sub("^[ \t]+","",Line)
  629.     if (Line == "")        # blank line
  630.         continue
  631.     if (Comment != "" && Line ~ Comment)
  632.         continue
  633.     if (Pos = index(Line,Assign)) {
  634.         Var = substr(Line,1,Pos-1)
  635.         Val = substr(Line,Pos+AssignLen)
  636.         if (StripWhite) {
  637.         sub("^[ \t]+","",Val)
  638.         sub("[ \t]+$","",Val)
  639.         }
  640.     }
  641.     else {
  642.         Var = Line    # If no value, var is entire line
  643.         Val = ""
  644.     }
  645.     if (!FlagsOK && Val == "") {
  646.         Errs = Errs \
  647.         sprintf("\nBad assignment on line %d of file %s: %s",
  648.         LineNum,File,Line)
  649.         continue
  650.     }
  651.     sub("[ \t]+$","",Var)
  652.     if (Var !~ VarPat) {
  653.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  654.         LineNum,File,Var)
  655.         continue
  656.     }
  657.     if (!(Var in Lines)) {
  658.         Lines[Var] = LineNum
  659.         if (Pos)
  660.         Values[Var] = Val
  661.     }
  662.     }
  663.     if (Status)
  664.     Errs = Errs "\nCould not read file " File
  665.     close(File)
  666.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  667. }
  668.  
  669. # Variables:
  670. # Data is stored in Options[].
  671. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  672. # Global vars:
  673. # Sets OptErr.  Uses ENVIRON[].
  674. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  675. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  676. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  677. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  678.     split("",filesRead,"")    # make awk know this is an array
  679.     NumVars = split(VarNames,Vars,",")
  680.     TypesInd = Ret = 0
  681.     if (EnvSearch == -1)
  682.     EnvSearch = NumVars
  683.     for (i = 1; i <= NumVars; i++) {
  684.     Var = Vars[i]
  685.     CharOpt = substr(OptList,++TypesInd,1)
  686.     if (CharOpt ~ "^[:;*()#<>&]$")
  687.         CharOpt = substr(OptList,++TypesInd,1)
  688.     Map[Var] = CharOpt
  689.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  690.     # Do not overwrite entries from environment
  691.     if (i <= EnvSearch && Var in ENVIRON &&
  692.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  693.         return Err
  694.     }
  695.  
  696.     numrcFiles = split(rcFiles,fNames,":")
  697.     for (i = 1; i <= numrcFiles; i++) {
  698.     rcFile = fNames[i]
  699.     if (rcFile ~ "^~/")
  700.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  701.     else if (rcFile ~ /^\$/) {
  702.         rcFile = substr(rcFile,2)
  703.         match(rcFile,"^[a-zA-Z0-9_]*")
  704.         envvar = substr(rcFile,1,RLENGTH)
  705.         if (envvar in ENVIRON)
  706.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  707.         else
  708.         continue
  709.     }
  710.     if (rcFile in filesRead)
  711.         continue
  712.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  713.     # may be the same
  714.     filesRead[rcFile]
  715.     if ("x" in Options)
  716.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  717.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  718.     if (retStr > 0)
  719.         READ_RCFILE = 1
  720.     else if (ret != "") {
  721.         OptErr = retStr
  722.         Ret = -1
  723.     }
  724.     for (Var in Lines)
  725.         if (Var in Map) {
  726.         if ((Err = AssignVal(Map[Var],
  727.         Var in Values ? Values[Var] : "",Options,Types[Var],
  728.         Var in Values,Var,0)) < 0)
  729.             return Err
  730.         }
  731.         else {
  732.         OptErr = sprintf(\
  733.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  734.         Lines[Var],rcFile)
  735.         Ret = -1
  736.         }
  737.     }
  738.  
  739.     if ("x" in Options)
  740.     for (Var in Map)
  741.         if (Map[Var] in Options)
  742.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  743.         "/dev/stderr"
  744.         else
  745.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  746.     return Ret
  747. }
  748.  
  749. # OptSets is a semicolon-separated list of sets of option sets.
  750. # Within a list of option sets, the option sets are separated by commas.  For
  751. # each set of sets, if any option in one of the sets is in Options[] AND any
  752. # option in one of the other sets is in Options[], an error string is returned.
  753. # If no conflicts are found, nothing is returned.
  754. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  755. # the exclusions presented by the first set of sets (ab,def,g) if:
  756. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  757. # (a or b is in Options[]) AND (g is in Options) OR
  758. # (d, e, or f is in Options[]) AND (g is in Options)
  759. # An error will be returned due to the exclusions presented by the second set
  760. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  761. # todo: make options given on command line unset options given in config file
  762. # todo: that they conflict with.
  763. function ExclusiveOptions(OptSets,Options,
  764. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  765. SetNum,OSetNum) {
  766.     NumSetSets = split(OptSets,SetSets,";")
  767.     # For each set of sets...
  768.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  769.     # NumSets is the number of sets in this set of sets.
  770.     NumSets = split(SetSets[SetSet],Sets,",")
  771.     # For each set in a set of sets except the last...
  772.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  773.         s1 = Sets[SetNum]
  774.         L1 = length(s1)
  775.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  776.         # If any of the options in this set was given, check whether
  777.         # any of the options in the other sets was given.  Only check
  778.         # later sets since earlier sets will have already been checked
  779.         # against this set.
  780.         if ((c1 = substr(s1,Pos1,1)) in Options)
  781.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  782.             s2 = Sets[OSetNum]
  783.             L2 = length(s2)
  784.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  785.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  786.                 ErrStr = ErrStr "\n"\
  787.                 sprintf("Cannot give both %s and %s options.",
  788.                 c1,c2)
  789.             }
  790.     }
  791.     }
  792.     if (ErrStr != "")
  793.     return substr(ErrStr,2)
  794.     return ""
  795. }
  796.  
  797. # The value of each instance of option Opt that occurs in Options[] is made an
  798. # index of Set[].
  799. # The return value is the number of instances of Opt in Options.
  800. function Opt2Set(Options,Opt,Set,  count) {
  801.     if (!(Opt in Options))
  802.     return 0
  803.     Set[Options[Opt]]
  804.     count = Options[Opt,"count"]
  805.     for (; count > 1; count--)
  806.     Set[Options[Opt,count]]
  807.     return count
  808. }
  809.  
  810. # The value of each instance of option Opt that occurs in Options[] that
  811. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  812. # Other values are made indexes of Set[].
  813. # The return value is the number of instances of Opt in Options.
  814. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  815.     ret = Opt2Set(Options,Opt,aSet)
  816.     for (value in aSet)
  817.     if (substr(value,1,1) == "!")
  818.         nSet[substr(value,2)]
  819.     else
  820.         Set[value]
  821.     return ret
  822. }
  823.  
  824. # Returns true if option Opt was given on the command line.
  825. function CmdLineOpt(Options,Opt,  i) {
  826.     for (i = 1; (Opt,"num",i) in Options; i++)
  827.     if (Options[Opt,"num",i] != 0)
  828.         return 1
  829.     return 0
  830. }
  831. ### End of ProcArgs library
  832.  
  833. function FileErr(S) {
  834.     printf "File %s, line %d: %s.\n%s\n",FILENAME,FNR,S,$0 > "/dev/stderr"
  835. }
  836.  
  837.