home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / mlookup < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  38.0 KB  |  1,077 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # mlookup: tell what a mail alias will expand to.
  4. # @(#) mlookup.gawk 2.1 97/02/19
  5. # 92/09/27 John DuBois (john@armory.com)
  6. #          Was a shell script that invoked mail.
  7. #          Rewrote as #!awk script; if mail is used,
  8. #          filter out "unknown address" and "nameserver timeout";
  9. #          added checkaddr capability & made it the default;
  10. #          removed 'whatis' link because whatis is now an OS command
  11. # 92/10/25 Added expansion of foo-outbound@list-processor and filtered lists
  12. # 93/06/08 Close Cmd in CheckAddrExpandName()
  13. # 93/12/07 Changed name from whois to mlookup; conflicted with whois command
  14. # 94/03/09 Use gawk so - options can be given & /dev/stderr used
  15. # 94/06/05 Added x option
  16. # 95/08/11 Added n option.
  17. # 96/05/03 Treat list-e-processor the same as list-processor
  18. # 96/11/01 Skip over args to listfilter
  19. # 97/02/19 Read addresses from stdin if none given on command line
  20.  
  21. BEGIN {
  22.     Name = "mlookup"
  23.     Usage = "Usage: " Name " [-nhoax] [mail-address ...]"
  24.     ARGC = Opts(Name,Usage,"nhoax",0)
  25.     if (Options["h"]) {
  26.         printf \
  27. "%s: tell what mail addresses will expand to.\n"\
  28. "%s\n"\
  29. "If no addresses are given on the command line, they are read from the\n"\
  30. "standard input.  Multiple addresses may be given on each input line. \n"\
  31. "Addresses may be separated by whitespace and/or commas.\n"\
  32. "Options:\n"\
  33. "-h: Print this help.\n"\
  34. "-o: If an alias expands to more than one recipient address, print one\n"\
  35. "    address per line.  The default is to print as many as will fit in 79\n"\
  36. "    columns.\n"\
  37. "-a: Use mail to look up addresses instead of checkaddr.  Mail lookup\n"\
  38. "    displays names as they are recorded in mailing lists.  The default is\n"\
  39. "    to use checkaddr, which does a more thorough expansion of aliases,\n"\
  40. "    determining who mail will actually be sent to.  Some mailers will not\n"\
  41. "    accept the ~ command used to expand aliases if they are invoked\n"\
  42. "    non-interactively, as this utility does.\n"\
  43. "-n: Always include the name of the name being looked up.  Without this\n"\
  44. "    option, the name is included only if more than one name is being\n"\
  45. "    looked up.\n"\
  46. "-x: Turns on debugging.\n",
  47.     Name,Usage
  48.     exit(0)
  49.     }
  50.     if (useMail = Options["a"]) {
  51.     FS = ",[ \t]*"
  52.     # Get rid of these
  53.     MakeSet(BadNames,"(Nameserver Timeout),(Unknown address)," \
  54.     "(Nameserver,Timeout),(Unknown,address),",",")
  55.     }
  56.     else
  57.     FS="'"
  58.  
  59.     Debug = "x" in Options
  60.     OnePerLine = "o" in Options
  61.     if (ARGC < 2)
  62.     while (getline line == 1) {
  63.         num = split(line,elem,"[ \t,]+")
  64.         for (AliasNum = 1; AliasNum <= num; AliasNum++)
  65.         doAlias(elem[AliasNum],useMail,OnePerLine,1,BadNames)
  66.     }
  67.     else {
  68.     IncName = ("n" in Options) || ARGC > 2
  69.     for (AliasNum = 1; AliasNum < ARGC; AliasNum++)
  70.         doAlias(ARGV[AliasNum],useMail,OnePerLine,IncName,BadNames)
  71.     }
  72. }
  73.  
  74. function doAlias(Alias,useMail,onePerLine,incName,BadNames,  Prefix,Addrs) {
  75.     # If more than one name is being expanded, print the name before
  76.     # what it expanded to.
  77.     if (incName) {
  78.     printf Newline
  79.     Newline = "\n"
  80.     if (onePerLine)
  81.         printf "%s:\n",Alias
  82.     else
  83.         Prefix = Alias ": "
  84.     }
  85.     Addrs[""]
  86.     split("",Addrs)    # let gawk know this is an array
  87.     if (ExpandName(Alias,useMail,BadNames,Addrs))
  88.     PrintAddrs(Addrs,onePerLine,Prefix)
  89. }
  90.  
  91. function ExpandName(Alias,UseMail,BadNames,Addrs,  NotAddr,Addr,Elem,i) {
  92.     DebugPrint("ExpandName looking up \"" Alias "\"")
  93.     delete Addrs[""]
  94.     if (UseMail) {
  95.     MailExpandName(Alias,Addrs)
  96.     SubtractSet(Addrs,BadNames)
  97.     }
  98.     else {
  99.     Recurse = 1
  100.     if ((NotAddr = CheckaddrExpandName(Alias,Addrs)) != "") {
  101.         printf "%s",NotAddr
  102.         return 0
  103.     }
  104.     }
  105.     for (Addr in Addrs) {
  106.     if (Addr ~ /^mmdf\|\/local\/admin\/listfilter /) {
  107.         split(Addr,Elem,"[ \t]+")
  108.         delete Addrs[Addr]
  109.         # Skip over args to listfilter
  110.         for (i = 2; i in Elem; i++)
  111.         if (Elem[i] !~ /^-/)
  112.             break
  113.         if (!(i in Elem))
  114.         return 0
  115.         if (!ExpandName(Elem[i] "-outbound",UseMail,BadNames,Addrs))
  116.         return 0
  117.     }
  118.     }
  119.     return 1
  120. }
  121.  
  122. function CheckaddrExpandName(Alias,Addrs,
  123. Cmd,GoodAddr,NotAddr,Addr,Ret,Line,LocAddrs) {
  124.     Cmd = "/usr/mmdf/bin/checkaddr -w " Alias " 2>&1"
  125.     delete Addrs[""]
  126.     DebugPrint("CheckaddrExpandName looking up \"" Alias "\"")
  127.     while ((Cmd | getline) == 1) {
  128.     if ($0 ~ /via/) {
  129.         Addr = $(NF - 1)
  130.         DebugPrint("Address returned for " Alias ": " Addr)
  131.         if (Addr in LocAddrs) {
  132.         # This seems to happen randomly
  133.         DebugPrint("Found " Addr " in " Alias " again; line was:\n"\
  134.         $0)
  135.         continue
  136.         }
  137.         LocAddrs[Addr]
  138.         if (Addr ~ "-outbound@list-(e-)?processor$") {
  139.         if (Addr == Alias "@list-(e-)?processor") {
  140.             DebugPrint("Found " Addr " in alias " Alias "; line was:\n"\
  141.             $0)
  142.             continue
  143.         }
  144.         DebugPrint("This mailing list is handed by the list channel")
  145.         # Limit to 3 levels of recursion to detect loops
  146.         if (Recurse > 3)
  147.             NotAddr = NotAddr "Bad mailing list: " Addr "\n"
  148.         else {
  149.             gsub("@list-(e-)?processor$","",Addr)
  150.             Recurse++
  151.             if ((Ret = CheckaddrExpandName(Addr,Addrs)) == "")
  152.             GoodAddr = 1
  153.             else
  154.             NotAddr = NotAddr Ret "\n"
  155.             Recurse--
  156.         }
  157.         }
  158.         else {
  159.         Addrs[Addr]
  160.         GoodAddr = 1
  161.         }
  162.     }
  163.     else     # Lines that don't end in ' are informational
  164.         NotAddr = NotAddr $0 "\n"
  165.     }
  166.     close(Cmd)
  167.     if (GoodAddr == 1)
  168.     return ""
  169.     else
  170.     return NotAddr
  171. }
  172.  
  173. function MailExpandName(Alias,Addrs,  Cmd,AliasPat,i) {
  174.     DebugPrint("MailExpandName looking up \"" Alias "\"")
  175.     # Set save, and then unset it, to avoid complaints
  176.     # about "undefined variable".  Don't want it set because
  177.     # mail will save a null message into dead.letter if save is set.
  178.     # Give a subject so it won't be prompted for.
  179.     # Make it be one space long because some mailers will prompt for subject
  180.     # if it is 0-length.
  181.     Cmd = "echo '~:set save nosave\n~A " Alias "\n~q' | mail -s ' ' /dev/null"
  182.     # If an error occurs, the first & only line written will begin
  183.     # with the name queried on
  184.     AliasPat = "^ *" Alias " \\(" 
  185.     DebugPrint("Running: " Cmd)
  186.     Cmd | getline
  187.     DebugPrint("Read: " $0)
  188.     if ($0 ~ AliasPat) {
  189.     print $0
  190.     close(Cmd)
  191.     return
  192.     }
  193.     sub(" +","")    # Get rid of leading spaces
  194.     # Process the first line read
  195.     for (i = 1; i <= NF; i++)
  196.     if ($i != "")
  197.         Addrs[$i]
  198.     while ((Cmd | getline) == 1) {
  199.     DebugPrint("Read: " $0)
  200.     sub(" +","")    # Get rid of leading spaces
  201.     for (i = 1; i <= NF; i++)
  202.         if ($i != "")
  203.         Addrs[$i]
  204.     }
  205.     close(Cmd)
  206. }
  207.  
  208. function PrintAddrs(Addrs,OnePerLine,Prefix,  i,NumAddrs,List,k,Num,Lines) {
  209.     NumAddrs = qsortByArbIndex(Addrs,k)    # Generate sorted map
  210.     if (OnePerLine) {
  211.     for (i = 1; i <= NumAddrs; i++)
  212.         print k[i]
  213.     return
  214.     }
  215.     List = Prefix k[1]
  216.     for (Num = 2; Num <= NumAddrs; Num++)
  217.     List = List ", " k[Num]
  218.     # Last name may be followed by this message without being
  219.     # separated from it by a comma
  220.     sub("\\(Nameserver Timeout\\)","",List)
  221.     Num = SplitLine(List,Lines,79,"^ ")
  222.     for (i = 1; i <= Num; i++)
  223.     print Lines[i]
  224. }
  225.  
  226. # Splits Line into lines with maximum length of MaxLen.
  227. # If WordChars is non-empty, words are split on characters other than
  228. # those in the set it describes, if possible.
  229. # The default for WordChars is:  a-zA-Z0-9_
  230. # Lines are split on a non-alphanum character if possible.
  231. # The lines are put into Lines with indices starting with 1.
  232. # The number of lines put in Lines is returned.
  233. function SplitLine(Line,Lines,MaxLen,WordChars,
  234. Len,LineNum,MaxLine) {
  235.     if (WordChars == "")
  236.     WordChars = "a-zA-Z0-9_"
  237.     WordChars = "[" WordChars "]"
  238.     LineNum = 0
  239.     while (Len = length(Line)) {
  240.     Lines[++LineNum] = substr(Line,1,MaxLen)
  241.     if (substr(Line,MaxLen,2) ~ "^" WordChars WordChars) {
  242.         MaxLine = Lines[LineNum]
  243.         sub(WordChars "*$","",MaxLine)
  244.         if (MaxLine != "")
  245.         Lines[LineNum] = MaxLine
  246.     }
  247.     Line = substr(Line,length(Lines[LineNum]) + 1)
  248.     sub("^ *","",Lines[LineNum])
  249.     }
  250.     return LineNum
  251. }
  252.  
  253. function DebugPrint(S) {
  254.     if (Debug)
  255.     print("*** " S) > "/dev/stderr"
  256. }
  257.  
  258. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  259. function SubtractSet(Minuend,Subtrahend,  Elem) {
  260.     for (Elem in Subtrahend)
  261.     delete Minuend[Elem]
  262. }
  263.  
  264. # MakeSet: make a set from a list.
  265. # An index with the name of each element of the list
  266. # is created in the given array.
  267. # Input variables: 
  268. # Elements is a string containing the list of elements.
  269. # Sep is the character that separates the elements of the list.
  270. # Output variables:
  271. # Set is the array.
  272. function MakeSet(Set,Elements,Sep,  Num,Names) {
  273.     Num = split(Elements,Names,Sep)
  274.     for (; Num; Num--)
  275.         Set[Names[Num]];
  276. }
  277.  
  278. ### Start of ProcArgs library
  279. # @(#) ProcArgs 1.11 96/12/08
  280. # 92/02/29 john h. dubois iii (john@armory.com)
  281. # 93/07/18 Added "#" arg type
  282. # 93/09/26 Do not count -h against MinArgs
  283. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  284. #          Removed meaning of "+" or "-" by itself.
  285. # 94/03/08 Added & option and *()< option types.
  286. # 94/04/02 Added NoRCopt to Opts()
  287. # 94/06/11 Mark numeric variables as such.
  288. # 94/07/08 Opts(): Do not require any args if h option is given.
  289. # 95/01/22 Record options given more than once.  Record option num in argv.
  290. # 95/06/08 Added ExclusiveOptions().
  291. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  292. #          Expand $VARNAME at the start of its filenames.
  293. #          Let varname=0 and -option- turn off an option.
  294. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  295. #          of the vars should be searched for in the environment.
  296. #          Check for duplicate rcfiles.
  297. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  298. #          now return various negatives values on error, not just -1, and
  299. #          Opts() may set Err to various positive values, not just 1.
  300. #          Added AllowUnrecOpt.
  301. # 96/05/23 Check type given for & option
  302. # 96/06/15 Re-port to awk
  303. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  304. #          used by other functions.
  305. # 96/10/15 Added OptChars
  306. # 96/11/01 Added exOpts arg to Opts()
  307. # 96/11/16 Added ; type
  308. # 96/12/08 Added Opt2Set() & Opt2Sets()
  309. # 96/12/27 Added CmdLineOpt()
  310.  
  311. # optlist is a string which contains all of the possible command line options.
  312. # A character followed by certain characters indicates that the option takes
  313. # an argument, with type as follows:
  314. # :    String argument
  315. # ;    Non-empty string argument
  316. # *    Floating point argument
  317. # (    Non-negative floating point argument
  318. # )    Positive floating point argument
  319. # #    Integer argument
  320. # <    Non-negative integer argument
  321. # >    Positive integer argument
  322. # The only difference the type of argument makes is in the runtime argument
  323. # error checking that is done.
  324.  
  325. # The & option is a special case used to get numeric options without the
  326. # user having to give an option character.  It is shorthand for [-+.0-9].
  327. # If & is included in optlist and an option string that begins with one of
  328. # these characters is seen, the value given to "&" will include the first
  329. # char of the option.  & must be followed by a type character other than ":"
  330. # or ";".
  331. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  332.  
  333. # Strings in argv[] which begin with "-" or "+" are taken to be
  334. # strings of options, except that a string which consists solely of "-"
  335. # or "+" is taken to be a non-option string; like other non-option strings,
  336. # it stops the scanning of argv and is left in argv[].
  337. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  338. # If an option takes an argument, the argument may either immediately
  339. # follow it or be given separately.
  340. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  341. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  342. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  343. # this feature had a flaw that caused problems in some cases.  See the OptChars
  344. # parameter to explicitly set the option-specifier characters.
  345.  
  346. # If an option that does not take an argument is given,
  347. # an index with its name is created in Options and its value is set to the
  348. # number of times it occurs in argv[].
  349.  
  350. # If an option that does take an argument is given, an index with its name is
  351. # created in Options and its value is set to the value of the argument given
  352. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  353. # If an option that takes an argument is given more than once,
  354. # Options[option-name,"count"] is incremented, and the value is assigned to
  355. # the index (option-name,instance) where instance is 2 for the second occurance
  356. # of the option, etc.
  357. # In other words, the first time an option with a value is encountered, the
  358. # value is assigned to an index consisting only of its name; for any further
  359. # occurances of the option, the value index has an extra (count) dimension.
  360.  
  361. # The sequence number for each option found in argv[] is stored in
  362. # Options[option-name,"num",instance], where instance is 1 for the first
  363. # occurance of the option, etc.  The sequence number starts at 1 and is
  364. # incremented for each option, both those that have a value and those that
  365. # do not.  Options set from a config file have a value of 0 assigned to this.
  366.  
  367. # Options and their arguments are deleted from argv.
  368. # Note that this means that there may be gaps left in the indices of argv[].
  369. # If compress is nonzero, argv[] is packed by moving its elements so that
  370. # they have contiguous integer indices starting with 0.
  371. # Option processing will stop with the first unrecognized option, just as
  372. # though -- was given except that unlike -- the unrecognized option will not be
  373. # removed from ARGV[].  Normally, an error value is returned in this case.
  374. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  375. # be found, so the number of remaining arguments is returned instead.
  376. # If OptChars is not a null string, it is the set of characters that indicate
  377. # that an argument is an option string if the string begins with one of the
  378. # characters.  A string consisting solely of two of the same option-indicator
  379. # characters stops the scanning of argv[].  The default is "-+".
  380. # argv[0] is not examined.
  381. # The number of arguments left in argc is returned.
  382. # If an error occurs, the global string OptErr is set to an error message
  383. # and a negative value is returned.
  384. # Current error values:
  385. # -1: option that required an argument did not get it.
  386. # -2: argument of incorrect type supplied for an option.
  387. # -3: unrecognized (invalid) option.
  388. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  389. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  390. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  391. {
  392. # ArgNum is the index of the argument being processed.
  393. # ArgsLeft is the number of arguments left in argv.
  394. # Arg is the argument being processed.
  395. # ArgLen is the length of the argument being processed.
  396. # ArgInd is the position of the character in Arg being processed.
  397. # Option is the character in Arg being processed.
  398. # Pos is the position in OptList of the option being processed.
  399. # NumOpt is true if a numeric option may be given.
  400.     ArgsLeft = argc
  401.     NumOpt = index(OptList,"&")
  402.     OptionNum = 0
  403.     if (OptChars == "")
  404.     OptChars = "-+"
  405.     while (OptChars != "") {
  406.     c = substr(OptChars,1,1)
  407.     OptChars = substr(OptChars,2)
  408.     OptCharSet[c]
  409.     OptTerm[c c]
  410.     }
  411.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  412.     Arg = argv[ArgNum]
  413.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  414.         break    # Not an option; quit
  415.     if (Arg in OptTerm) {
  416.         delete argv[ArgNum]
  417.         ArgsLeft--
  418.         break
  419.     }
  420.     ArgLen = length(Arg)
  421.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  422.         Option = substr(Arg,ArgInd,1)
  423.         if (NumOpt && Option ~ /[-+.0-9]/) {
  424.         # If this option is a numeric option, make its flag be & and
  425.         # its option string flag position be the position of & in
  426.         # the option string.
  427.         Option = "&"
  428.         Pos = NumOpt
  429.         # Prefix Arg with a char so that ArgInd will point to the
  430.         # first char of the numeric option.
  431.         Arg = "&" Arg
  432.         ArgLen++
  433.         }
  434.         # Find position of flag in option string, to get its type (if any).
  435.         # Disallow & as literal flag.
  436.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  437.         if (AllowUnrecOpt) {
  438.             Escape = 1
  439.             break
  440.         }
  441.         else {
  442.             OptErr = "Invalid option: " specGiven Option
  443.             return -3
  444.         }
  445.         }
  446.  
  447.         # Find what the value of the option will be if it takes one.
  448.         # NeedNextOpt is true if the option specifier is the last char of
  449.         # this arg, which means that if the option requires a value it is
  450.         # the next arg.
  451.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  452.         if (GotValue = ArgNum + 1 < argc)
  453.             Value = argv[ArgNum+1]
  454.         }
  455.         else {    # Value is included with option
  456.         Value = substr(Arg,ArgInd + 1)
  457.         GotValue = 1
  458.         }
  459.  
  460.         if (HadValue = AssignVal(Option,Value,Options,
  461.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  462.         specGiven)) {
  463.         if (HadValue < 0)    # error occured
  464.             return HadValue
  465.         if (HadValue == 2)
  466.             ArgInd++    # Account for the single-char value we used.
  467.         else {
  468.             if (NeedNextOpt) {    # option took next arg as value
  469.             delete argv[++ArgNum]
  470.             ArgsLeft--
  471.             }
  472.             break    # This option has been used up
  473.         }
  474.         }
  475.     }
  476.     if (Escape)
  477.         break
  478.     # Do not delete arg until after processing of it, so that if it is not
  479.     # recognized it can be left in ARGV[].
  480.     delete argv[ArgNum]
  481.     ArgsLeft--
  482.     }
  483.     if (compress != 0) {
  484.     dest = 1
  485.     src = argc - ArgsLeft + 1
  486.     for (count = ArgsLeft - 1; count; count--) {
  487.         ARGV[dest] = ARGV[src]
  488.         dest++
  489.         src++
  490.     }
  491.     }
  492.     return ArgsLeft
  493. }
  494.  
  495. # Assignment to values in Options[] occurs only in this function.
  496. # Option: Option specifier character.
  497. # Value: Value to be assigned to option, if it takes a value.
  498. # Options[]: Options array to return values in.
  499. # ArgType: Argument type specifier character.
  500. # GotValue: Whether any value is available to be assigned to this option.
  501. # Name: Name of option being processed.
  502. # OptionNum: Number of this option (starting with 1) if set in argv[],
  503. #     or 0 if it was given in a config file or in the environment.
  504. # SingleOpt: true if the value (if any) that is available for this option was
  505. #     given as part of the same command line arg as the option.  Used only for
  506. #     options from the command line.
  507. # specGiven is the option specifier character use, if any (e.g. - or +),
  508. # for use in error messages.
  509. # Global variables: OptErr
  510. # Return value: negative value on error, 0 if option did not require an
  511. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  512. # the arg.
  513. # Current error values:
  514. # -1: Option that required an argument did not get it.
  515. # -2: Value of incorrect type supplied for option.
  516. # -3: Bad type given for option &
  517. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  518. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  519.     # If option takes a value...    [
  520.     NumTypes = "*()#<>]"
  521.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  522.     OptErr = "Bad type given for & option"
  523.     return -3
  524.     }
  525.  
  526.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  527.     if (!GotValue) {
  528.         if (Name != "")
  529.         OptErr = "Variable requires a value -- " Name
  530.         else
  531.         OptErr = "option requires an argument -- " Option
  532.         return -1
  533.     }
  534.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  535.         OptErr = Err
  536.         return -2
  537.     }
  538.     # Mark this as a numeric variable; will be propogated to Options[] val.
  539.     if (ArgType != ":" && ArgType != ";")
  540.         Value += 0
  541.     if ((Instance = ++Options[Option,"count"]) > 1)
  542.         Options[Option,Instance] = Value
  543.     else
  544.         Options[Option] = Value
  545.     }
  546.     # If this is an environ or rcfile assignment & it was given a value...
  547.     else if (!OptionNum && Value != "") {
  548.     UsedValue = 1
  549.     # If the value is "0" or "-" and this is the first instance of it,
  550.     # do not set Options[Option]; this allows an assignment in an rcfile to
  551.     # turn off an option (for the simple "Option in Options" test) in such
  552.     # a way that it cannot be turned on in a later file.
  553.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  554.         Instance = 1
  555.     else
  556.         Instance = ++Options[Option]
  557.     # Save the value even though this is a flag
  558.     Options[Option,Instance] = Value
  559.     }
  560.     # If this is a command line flag and has a - following it in the same arg,
  561.     # it is being turned off.
  562.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  563.     UsedValue = 2
  564.     if (Option in Options)
  565.         Instance = ++Options[Option]
  566.     else
  567.         Instance = 1
  568.     Options[Option,Instance]
  569.     }
  570.     # If this is a flag assignment without a value, increment the count for the
  571.     # flag unless it was turned off.  The indicator for a flag being turned off
  572.     # is that the flag index has not been set in Options[] but it has an
  573.     # instance count.
  574.     else if (Option in Options || !((Option,1) in Options))
  575.     # Increment number of times this flag seen; will inc null value to 1
  576.     Instance = ++Options[Option]
  577.     Options[Option,"num",Instance] = OptionNum
  578.     return UsedValue
  579. }
  580.  
  581. # Option is the option letter
  582. # Value is the value being assigned
  583. # Name is the var name of the option, if any
  584. # ArgType is one of:
  585. # :    String argument
  586. # ;    Non-null string argument
  587. # *    Floating point argument
  588. # (    Non-negative floating point argument
  589. # )    Positive floating point argument
  590. # #    Integer argument
  591. # <    Non-negative integer argument
  592. # >    Positive integer argument
  593. # specGiven is the option specifier character use, if any (e.g. - or +),
  594. # for use in error messages.
  595. # Returns null on success, err string on error
  596. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  597.     if (ArgType == ":")
  598.     return ""
  599.     if (ArgType == ";") {
  600.     if (Value == "")
  601.         Err = "must be a non-empty string"
  602.     }
  603.     # A number begins with optional + or -, and is followed by a string of
  604.     # digits or a decimal with digits before it, after it, or both
  605.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  606.     Err = "must be a number"
  607.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  608.     Err = "may not include a fraction"
  609.     else if (ArgType ~ "[()<>]" && Value < 0)
  610.     Err = "may not be negative"
  611.     # (
  612.     else if (ArgType ~ "[)>]" && Value == 0)
  613.     Err = "must be a positive number"
  614.     if (Err != "") {
  615.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  616.     if (Name != "")
  617.         return ErrStr "variable " substr(Name,1,1) " " Err
  618.     else {
  619.         if (Option == "&")
  620.         Option = Value
  621.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  622.     }
  623.     }
  624.     else
  625.     return ""
  626. }
  627.  
  628. # Note: only the above functions are needed by ProcArgs.
  629. # The rest of these functions call ProcArgs() and also do other
  630. # option-processing stuff.
  631.  
  632. # Opts: Process command line arguments.
  633. # Opts processes command line arguments using ProcArgs()
  634. # and checks for errors.  If an error occurs, a message is printed
  635. # and the program is exited.
  636. #
  637. # Input variables:
  638. # Name is the name of the program, for error messages.
  639. # Usage is a usage message, for error messages.
  640. # OptList the option description string, as used by ProcArgs().
  641. # MinArgs is the minimum number of non-option arguments that this
  642. # program should have, non including ARGV[0] and +h.
  643. # If the program does not require any non-option arguments,
  644. # MinArgs should be omitted or given as 0.
  645. # rcFiles, if given, is a colon-seprated list of filenames to read for
  646. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  647. # by the value of the environment variable HOME.  If a filename begins with
  648. # $, the part from the character after the $ up until (but not including)
  649. # the first character not in [a-zA-Z0-9_] will be searched for in the
  650. # environment; if found its value will be substituted, if not the filename will
  651. # be discarded.
  652. # rcfiles are read in the order given.
  653. # Values given in them will not override values given on the command line,
  654. # and values given in later files will not override those set in earlier
  655. # files, because AssignVal() will store each with a different instance index.
  656. # The first instance of each variable, either on the command line or in an
  657. # rcfile, will be stored with no instance index, and this is the value
  658. # normally used by programs that call this function.
  659. # VarNames is a comma-separated list of variable names to map to options,
  660. # in the same order as the options are given in OptList.
  661. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  662. # searched for in the environment.  If set to -1, all values will be searched
  663. # for in the environment.  Values given in the environment will override
  664. # those given in the rcfiles but not those given on the command line.
  665. # NoRCopt, if given, is an additional letter option that if given on the
  666. # command line prevents the rcfiles from being read.
  667. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  668. # ExclusiveOptions() for a description of exOpts.
  669. # Special options:
  670. # If x is made an option and is given, some debugging info is output.
  671. # h is assumed to be the help option.
  672.  
  673. # Global variables:
  674. # The command line arguments are taken from ARGV[].
  675. # The arguments that are option specifiers and values are removed from
  676. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  677. # The number of elements in ARGV[] should be in ARGC.
  678. # After processing, ARGC is set to the number of elements left in ARGV[].
  679. # The option values are put in Options[].
  680. # On error, Err is set to a positive integer value so it can be checked for in
  681. # an END block.
  682. # Return value: The number of elements left in ARGV is returned.
  683. # Must keep OptErr global since it may be set by InitOpts().
  684. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  685. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  686.     if (MinArgs == "")
  687.     MinArgs = 0
  688.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  689.     optChars)
  690.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  691.     if (ArgsLeft >= 0) {
  692.         OptErr = "Not enough arguments"
  693.         Err = 4
  694.     }
  695.     else
  696.         Err = -ArgsLeft
  697.     printf "%s: %s.\nUse -h for help.\n%s\n",
  698.     Name,OptErr,Usage > "/dev/stderr"
  699.     exit 1
  700.     }
  701.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  702.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  703.     {
  704.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  705.     Err = -e
  706.     exit 1
  707.     }
  708.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  709.     {
  710.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  711.     Err = 1
  712.     exit 1
  713.     }
  714.     return ArgsLeft
  715. }
  716.  
  717. # ReadConfFile(): Read a file containing var/value assignments, in the form
  718. # <variable-name><assignment-char><value>.
  719. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  720. # line and whitespace between the variable name and the assignment character) 
  721. # is stripped.  Lines that do not contain an assignment operator or which
  722. # contain a null variable name are ignored, other than possibly being noted in
  723. # the return value.  If more than one assignment is made to a variable, the
  724. # first assignment is used.
  725. # Input variables:
  726. # File is the file to read.
  727. # Comment is the line-comment character.  If it is found as the first non-
  728. #     whitespace character on a line, the line is ignored.
  729. # Assign is the assignment string.  The first instance of Assign on a line
  730. #     separates the variable name from its value.
  731. # If StripWhite is true, whitespace around the value (whitespace between the
  732. #     assignment char and trailing whitespace on the line) is stripped.
  733. # VarPat is a pattern that variable names must match.  
  734. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  735. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  736. #     a line; no assignment operator is needed.  These variables are set in
  737. #     the output array with a null value.  Lines containing nothing but
  738. #     whitespace are still ignored.
  739. # Output variables:
  740. # Values[] contains the assignments, with the indexes being the variable names
  741. #     and the values being the assigned values.
  742. # Lines[] contains the line number that each variable occured on.  A flag set
  743. #     is record by giving it an index in Lines[] but not in Values[].
  744. # Return value:
  745. # If any errors occur, a string consisting of descriptions of the errors
  746. # separated by newlines is returned.  In no case will the string start with a
  747. # numeric value.  If no errors occur,  the number of lines read is returned.
  748. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  749. FlagsOK,
  750. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  751.     if (Comment != "")
  752.     Comment = "^" Comment
  753.     AssignLen = length(Assign)
  754.     if (VarPat == "")
  755.     VarPat = "."    # null varname not allowed
  756.     while ((Status = (getline Line < File)) == 1) {
  757.     LineNum++
  758.     sub("^[ \t]+","",Line)
  759.     if (Line == "")        # blank line
  760.         continue
  761.     if (Comment != "" && Line ~ Comment)
  762.         continue
  763.     if (Pos = index(Line,Assign)) {
  764.         Var = substr(Line,1,Pos-1)
  765.         Val = substr(Line,Pos+AssignLen)
  766.         if (StripWhite) {
  767.         sub("^[ \t]+","",Val)
  768.         sub("[ \t]+$","",Val)
  769.         }
  770.     }
  771.     else {
  772.         Var = Line    # If no value, var is entire line
  773.         Val = ""
  774.     }
  775.     if (!FlagsOK && Val == "") {
  776.         Errs = Errs \
  777.         sprintf("\nBad assignment on line %d of file %s: %s",
  778.         LineNum,File,Line)
  779.         continue
  780.     }
  781.     sub("[ \t]+$","",Var)
  782.     if (Var !~ VarPat) {
  783.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  784.         LineNum,File,Var)
  785.         continue
  786.     }
  787.     if (!(Var in Lines)) {
  788.         Lines[Var] = LineNum
  789.         if (Pos)
  790.         Values[Var] = Val
  791.     }
  792.     }
  793.     if (Status)
  794.     Errs = Errs "\nCould not read file " File
  795.     close(File)
  796.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  797. }
  798.  
  799. # Variables:
  800. # Data is stored in Options[].
  801. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  802. # Global vars:
  803. # Sets OptErr.  Uses ENVIRON[].
  804. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  805. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  806. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  807. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  808.     split("",filesRead,"")    # make awk know this is an array
  809.     NumVars = split(VarNames,Vars,",")
  810.     TypesInd = Ret = 0
  811.     if (EnvSearch == -1)
  812.     EnvSearch = NumVars
  813.     for (i = 1; i <= NumVars; i++) {
  814.     Var = Vars[i]
  815.     CharOpt = substr(OptList,++TypesInd,1)
  816.     if (CharOpt ~ "^[:;*()#<>&]$")
  817.         CharOpt = substr(OptList,++TypesInd,1)
  818.     Map[Var] = CharOpt
  819.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  820.     # Do not overwrite entries from environment
  821.     if (i <= EnvSearch && Var in ENVIRON &&
  822.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  823.         return Err
  824.     }
  825.  
  826.     numrcFiles = split(rcFiles,fNames,":")
  827.     for (i = 1; i <= numrcFiles; i++) {
  828.     rcFile = fNames[i]
  829.     if (rcFile ~ "^~/")
  830.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  831.     else if (rcFile ~ /^\$/) {
  832.         rcFile = substr(rcFile,2)
  833.         match(rcFile,"^[a-zA-Z0-9_]*")
  834.         envvar = substr(rcFile,1,RLENGTH)
  835.         if (envvar in ENVIRON)
  836.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  837.         else
  838.         continue
  839.     }
  840.     if (rcFile in filesRead)
  841.         continue
  842.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  843.     # may be the same
  844.     filesRead[rcFile]
  845.     if ("x" in Options)
  846.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  847.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  848.     if (retStr > 0)
  849.         READ_RCFILE = 1
  850.     else if (ret != "") {
  851.         OptErr = retStr
  852.         Ret = -1
  853.     }
  854.     for (Var in Lines)
  855.         if (Var in Map) {
  856.         if ((Err = AssignVal(Map[Var],
  857.         Var in Values ? Values[Var] : "",Options,Types[Var],
  858.         Var in Values,Var,0)) < 0)
  859.             return Err
  860.         }
  861.         else {
  862.         OptErr = sprintf(\
  863.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  864.         Lines[Var],rcFile)
  865.         Ret = -1
  866.         }
  867.     }
  868.  
  869.     if ("x" in Options)
  870.     for (Var in Map)
  871.         if (Map[Var] in Options)
  872.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  873.         "/dev/stderr"
  874.         else
  875.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  876.     return Ret
  877. }
  878.  
  879. # OptSets is a semicolon-separated list of sets of option sets.
  880. # Within a list of option sets, the option sets are separated by commas.  For
  881. # each set of sets, if any option in one of the sets is in Options[] AND any
  882. # option in one of the other sets is in Options[], an error string is returned.
  883. # If no conflicts are found, nothing is returned.
  884. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  885. # the exclusions presented by the first set of sets (ab,def,g) if:
  886. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  887. # (a or b is in Options[]) AND (g is in Options) OR
  888. # (d, e, or f is in Options[]) AND (g is in Options)
  889. # An error will be returned due to the exclusions presented by the second set
  890. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  891. # todo: make options given on command line unset options given in config file
  892. # todo: that they conflict with.
  893. function ExclusiveOptions(OptSets,Options,
  894. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  895. SetNum,OSetNum) {
  896.     NumSetSets = split(OptSets,SetSets,";")
  897.     # For each set of sets...
  898.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  899.     # NumSets is the number of sets in this set of sets.
  900.     NumSets = split(SetSets[SetSet],Sets,",")
  901.     # For each set in a set of sets except the last...
  902.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  903.         s1 = Sets[SetNum]
  904.         L1 = length(s1)
  905.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  906.         # If any of the options in this set was given, check whether
  907.         # any of the options in the other sets was given.  Only check
  908.         # later sets since earlier sets will have already been checked
  909.         # against this set.
  910.         if ((c1 = substr(s1,Pos1,1)) in Options)
  911.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  912.             s2 = Sets[OSetNum]
  913.             L2 = length(s2)
  914.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  915.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  916.                 ErrStr = ErrStr "\n"\
  917.                 sprintf("Cannot give both %s and %s options.",
  918.                 c1,c2)
  919.             }
  920.     }
  921.     }
  922.     if (ErrStr != "")
  923.     return substr(ErrStr,2)
  924.     return ""
  925. }
  926.  
  927. # The value of each instance of option Opt that occurs in Options[] is made an
  928. # index of Set[].
  929. # The return value is the number of instances of Opt in Options.
  930. function Opt2Set(Options,Opt,Set,  count) {
  931.     if (!(Opt in Options))
  932.     return 0
  933.     Set[Options[Opt]]
  934.     count = Options[Opt,"count"]
  935.     for (; count > 1; count--)
  936.     Set[Options[Opt,count]]
  937.     return count
  938. }
  939.  
  940. # The value of each instance of option Opt that occurs in Options[] that
  941. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  942. # Other values are made indexes of Set[].
  943. # The return value is the number of instances of Opt in Options.
  944. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  945.     ret = Opt2Set(Options,Opt,aSet)
  946.     for (value in aSet)
  947.     if (substr(value,1,1) == "!")
  948.         nSet[substr(value,2)]
  949.     else
  950.         Set[value]
  951.     return ret
  952. }
  953.  
  954. # Returns true if option Opt was given on the command line.
  955. function CmdLineOpt(Options,Opt,  i) {
  956.     for (i = 1; (Opt,"num",i) in Options; i++)
  957.     if (Options[Opt,"num",i] != 0)
  958.         return 1
  959.     return 0
  960. }
  961. ### End of ProcArgs library
  962. ### Begin qsort routines
  963.  
  964. # Arr[] is an array of values with arbitrary indices.
  965. # k[] is returned with numeric indices 1..n.
  966. # The values in k[] are the indices of Arr[],
  967. # ordered so that if Arr[] is stepped through
  968. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  969. # through in order of the values of its elements.
  970. # The return value is the number of elements in the arrays (n).
  971. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  972.     ElNum = 0
  973.     for (ArrInd in Arr)
  974.     k[++ElNum] = ArrInd
  975.     qsortSegment(Arr,k,1,ElNum)
  976.     return ElNum
  977. }
  978.  
  979. # Sort a segment of an array.
  980. # Arr[] contains data with arbitrary indices.
  981. # k[] has indices 1..nelem, with the indices of arr[] as values.
  982. # This function sorts the elements of arr that are pointed to by
  983. # k[start..end], swapping the values of elements of k[] so that
  984. # when this function returns arr[k[start..end]] will be in order.
  985. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  986.     # handle two-element case explicitly for a tiny speedup
  987.     if ((end - start) == 1) {
  988.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  989.         k[start] = tmpe
  990.         k[end] = tmps
  991.     }
  992.     return
  993.     }
  994.     # Make sure comparisons act on these as numbers
  995.     left = start+0
  996.     right = end+0
  997.     sepval = Arr[k[int((left + right) / 2)]]
  998.     # Make every element <= sepval be to the left of every element > sepval
  999.     while (left < right) {
  1000.     while (Arr[k[left]] < sepval)
  1001.         left++
  1002.     while (Arr[k[right]] > sepval)
  1003.         right--
  1004.     if (left < right) {
  1005.         tmp = k[left]
  1006.         k[left++] = k[right]
  1007.         k[right--] = tmp
  1008.     }
  1009.     }
  1010.     if (left == right)
  1011.     if (Arr[k[left]] < sepval)
  1012.         left++
  1013.     else
  1014.         right--
  1015.     if (start < right)
  1016.     qsortSegment(Arr,k,start,right)
  1017.     if (left < end)
  1018.     qsortSegment(Arr,k,left,end)
  1019. }
  1020.  
  1021. # Arr[] is an array of values with arbitrary indices.
  1022. # k[] is returned with numeric indices 1..n.
  1023. # The values in k are the indices of Arr[],
  1024. # ordered so that if Arr[] is stepped through
  1025. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1026. # through in order of the values of its indices.
  1027. # The return value is the number of elements in the arrays (n).
  1028. # If the indexes are numeric, Numeric should be true, so that they can be
  1029. # compared as such rather than as strings.  Numeric indexes do not have to be
  1030. # contiguous.
  1031. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1032.     ElNum = 0
  1033.     if (Numeric)
  1034.     # Indexes do not preserve numeric type, so must be forced
  1035.     for (ArrInd in Arr)
  1036.         k[++ElNum] = ArrInd+0
  1037.     else
  1038.     for (ArrInd in Arr)
  1039.         k[++ElNum] = ArrInd
  1040.     qsortNumIndByValue(k,1,ElNum)
  1041.     return ElNum
  1042. }
  1043.  
  1044. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1045. # by value.
  1046. # start and end are the starting and ending indexes of the range to be sorted.
  1047. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1048.     # handle two-element case explicitly for a tiny speedup
  1049.     if ((start - end) == 1) {
  1050.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1051.         Arr[start] = tmpe
  1052.         Arr[end] = tmps
  1053.     }
  1054.     return
  1055.     }
  1056.     left = start+0
  1057.     right = end+0
  1058.     sepval = Arr[int((left + right) / 2)]
  1059.     while (left < right) {
  1060.     while (Arr[left] < sepval)
  1061.         left++
  1062.     while (Arr[right] > sepval)
  1063.         right--
  1064.     if (left <= right) {
  1065.         tmp = Arr[left]
  1066.         Arr[left++] = Arr[right]
  1067.         Arr[right--] = tmp
  1068.     }
  1069.     }
  1070.     if (start < right)
  1071.     qsortNumIndByValue(Arr,start,right)
  1072.     if (left < end)
  1073.     qsortNumIndByValue(Arr,left,end)
  1074. }
  1075.  
  1076. ### End qsort routines
  1077.