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

  1. #!/usr/local/bin/gawk -f
  2. # @(#) uumail.gawk 1.4 96/09/10
  3. # 91/03/13 john h. dubois iii (john@armory.com)
  4. # 91/03/30 converted from a collection of ksh & awk programs
  5. #          to ksh preliminaries & one awk program
  6. # 91/10/28 fixed incompatibilities with gawk
  7. #          fixed bugs in parsing of user@machine, etc.
  8. #          added alias expansion
  9. #          changed to separate ksh & awk scripts when awk part got too big
  10. # 92/05/01 Changed to #!gawk script.  It has strftime(); use it instead of date.
  11. # 93/12/09 Send local mail through rmail, not execmail, for more useful test.
  12. #          Include mail to local uucp name or local domain names as local mail.
  13. #          Added f option.
  14. # 93/12/13 Optimized so that mail that will only be fed to one process is not
  15. #          stored, which can take a very long time in awk; it is read & 
  16. #          written to process.
  17. #          Use vales of MAILRC and MUSHRC.
  18. #          Put addressees on To: line.
  19. # 94/07/27 Added r option.
  20. # 96/03/18 Added b option.
  21. # 96/06/03 Added nix options; removed V option.
  22. # 96/09/10 Fixed x option and MUSHRC env var handling.  Added t option.
  23. # 97/02/03 Added T option.
  24. # 97/02/09 Added V and L options.
  25.  
  26. # uumail: send mail by uucp.   
  27. #
  28. # This script is used for debugging mail problems by bypassing the local mail
  29. # system and sending mail to users on remote systems by directly executing uux.
  30.  
  31. BEGIN {
  32.  
  33.     Name = "uumail"
  34.     Usage = "Usage:\n"\
  35. Name " [-binhrtT] [-V<log-level>] [-L<logfile>] [-s<subject>] [-S<sender>]\n"\
  36. "       recipient-address ..."
  37.     ARGC = Opts(Name,Usage,":bins:hrS;x>tTV;L;",1)
  38.     if ((Err = ExclusiveOptions("b,r",Options)) != "") {
  39.     printf "Error: %s\n",Err > "/dev/stderr"
  40.     Err = 1
  41.     exit(1)
  42.     }
  43.     # the -L option does not appear to work; submit recognizes it and says it
  44.     # has set the logfile to the given name, but still logs to default
  45. #"-L<logfile>: Have submit log to the file <logfile> instead of the logfile\n"\
  46. #"    specified in mmdftailor, or the default of /usr/mmdf/log/chan.log.  Use\n"\
  47. #"    -L/dev/tty to display the information instead of writing it to a file.\n"\
  48. #"    As with -V, -L can only be used by root or mmdf.\n"\
  49.     if ("h" in Options) {
  50.     printf \
  51. "%s: send mail by uucp.\n" \
  52. "%s\n"\
  53. "Recipients is a list of user addresses to which a message is to be sent.\n" \
  54. "Addresses must be of the form user@site, site![site! ... ]user, or user.\n" \
  55. "site must be known to the uucp system of this system.\n" \
  56. "In the last case, user must be a user on this system.\n" \
  57. "Alias expansion from .mailrc is done.\n"\
  58. "If no subject is given on the command line, a subject will be asked for.\n"\
  59. "Therefore, if " Name " is used non-interactively, a subject should always" \
  60. "be given on the command line.\n" \
  61. "The To: line of the message will the addressees before alias expansion,\n" \
  62. "truncated to 1024 characters.\n" \
  63. "Mail is sent to local recipients through rmail, and to remote recipients\n"\
  64. "through uux. \n"\
  65. "Options:\n" \
  66. "-r: Send mail to all recipients through rmail.\n"\
  67. "-s<subject>: Set the mail subject.\n" \
  68. "-h: Print this help.\n" \
  69. "-S<sender>: Set the address the mail is from for both the From_ and From:\n"\
  70. "    lines.\n"\
  71. "-x<level>: Set debugging to <level>.\n"\
  72. "-t: Generate test mail.  This prevents %s from reading the body of the\n"\
  73. "    message from the input; instead it is set to \"This is a test message.\"\n"\
  74. "    If -s is not given, the subject is set to \"Test Message\".\n"\
  75. "-b: Send mail to all recipients by directly invoking submit.\n"\
  76. "The following options can only be used in conjunction with -b:\n"\
  77. "-n: Do not allow message to be returned.\n"\
  78. "-T: Trust the author identification.  This will cause a Source-Info: field\n"\
  79. "    to be added to the header if the sender identity does not appear\n"\
  80. "    correct (without -T, submit will fail in that circumstance).\n"\
  81. "-V<log-level>: Set the logging level to <log-level>, which should be one\n"\
  82. "    of the MMDF log levels.  These may also be referred to as 1 through 8:\n"\
  83. "    FAT logs fatal errors only. TMP logs temporary errors and fatal errors.\n"\
  84. "    GEN logs generally interesting diagnostics.  BST logs basic statistics.\n"\
  85. "    FST gives full statistics.  PTR gives program trace listing.\n"\
  86. "    BTR give more detail tracing.  FTR gives every possible diagnostic.\n"\
  87. "    This option can only be used by root or MMDF.  Attempts to use it by\n"\
  88. "    non-privileged users will result in submit giving an error, usually\n"\
  89. "    'Invalid parameter character'.\n"\
  90. "-i: Deliver mail immediately, regardless of MMDF configuration.\n",
  91.     Name,Usage,Name
  92.     exit(0)
  93.     }
  94.  
  95.     Immediate = "i" in Options
  96.     TrustMe = "T" in Options
  97.     DoReturn = !("n" in Options)
  98.     UseSubmit = "b" in Options
  99.     TestMail = ("t" in Options)
  100.     if ("L" in Options)
  101.     logFile = Options["L"]
  102.     if ("V" in Options) {
  103.     levList = "FAT,TMP,GEN,BST,FST,PTR,BTR,FTR"
  104.     split(levList,levSet,",")
  105.     logLevel = toupper(Options["V"])
  106.     if (logLevel in levSet)
  107.         logLevel = levSet[logLevel]
  108.     else {
  109.         MakeSet(levNames,levList,",")
  110.         if (!(logLevel in levNames)) {
  111.         printf "%s: Bad logging level: %s.  Exiting.\n",
  112.         Name,logLevel > "/dev/stderr"
  113.         exit 1
  114.         }
  115.         printf "%s: Logging set to level %s\n",
  116.         Name,logLevel > "/dev/stderr"
  117.     }
  118.     }
  119.     if ((!DoReturn || TrustMe) && !UseSubmit) {
  120.     print "Must give -b option if -n or -T is used." > "/dev/stderr"
  121.     exit 1
  122.     }
  123.     if ("x" in Options) {
  124.     Debug = Options["x"]
  125.     printf "Debugging level set to %s\n",Debug > "/dev/stderr"
  126.     }
  127.     if ("s" in Options)
  128.     subject = Options["s"]
  129.     else if (TestMail)
  130.     subject = "Test Mail"
  131.  
  132.     GetMailName()    # set machine and domname
  133.     GetAliases(Aliases,Debug > 5)
  134.     SepMachine(ARGV,ARGC - 1,Aliases,users,Debug > 4)
  135.     if (Debug) {
  136.         printf "System mail name: %s; machine name: %s\n",domname,machine
  137.     if (locname != "")
  138.         printf "Local name: %s\n",locname
  139.         for (sysname in users)
  140.             printf "System: %s; users: %s\n",sysname,users[sysname]
  141.     }
  142.     # Ask for a subject if none given on cmd line;
  143.     # if subject was given on cmd line, print it
  144.     if (!TestMail) {
  145.     printf "Subject: "
  146.     if (subject == "") {
  147.         MesgIn()
  148.         subject = MesgLine
  149.     }
  150.     else
  151.         print subject 
  152.     print ""
  153.     }
  154.     # Create date line of the form  Wed, 13 Mar 91 01:46:09 PST
  155.     date = strftime("%a, %d %h %y %T")
  156.     date = date " " substr(ENVIRON["TZ"],length(ENVIRON["TZ"]) - 2)
  157.     # Create date line of the form  Wed Mar 13 01:46:09 1991
  158.     fromdate = strftime("%a %h %d %T 19%y")
  159.     if ("S" in Options) {
  160.     from = from_ = Options["S"]
  161.     }
  162.     else {
  163.     from_ = ENVIRON["USER"]
  164.     from = from_ "@" domname
  165.     }
  166.  
  167.     h1 = "From " from_ " " fromdate " remote from " machine "\n"
  168. #    if (!DoReturn)
  169. #    h1 = "\n"
  170.     h2 = "From: " ENVIRON["NAME"] " <" from ">\n"
  171.     h3 = "To:"
  172.     ToLen = length(h3)
  173.     for (i = 1; i < ARGC && (ToLen += length(ARGV[i]) + 1) < 1020; i++)
  174.     h3 = h3 " " ARGV[i]
  175.     h3 = h3 "\n"
  176.     if (subject != "")
  177.     h4 = "Subject: " subject "\n"
  178.     h5 = "Date: " date "\n"
  179.     h6 = "\n"
  180.     Header = h1 h2 h3 h4 h5 h6
  181.  
  182.     NumSys = 0
  183.     for (SysName in users)
  184.     if (++NumSys == 2)
  185.         break
  186.     OneSys = NumSys < 2
  187.  
  188.     if (TestMail)
  189.     message = "This is a test message."
  190.     else
  191.     message = ReadBody(OneSys)
  192.  
  193.     if (!OneSys || Debug)
  194.     printf "Mailing..."
  195.     for (System in users) {
  196.         SendMessage(System,OneSys && !TestMail,"r" in Options,UseSubmit,
  197.     DoReturn,Immediate,TrustMe,logLevel,logFile,Header,message,users)
  198.     if (Debug)
  199.         printf "\nSending mail for users on system %s...",System
  200.     }
  201.     print "Done."
  202.     exit 0
  203. }
  204.  
  205. function MesgIn () {
  206.     if ((getline MesgLine < "/dev/stdin") == 1 && (MesgLine != "."))
  207.     return 1
  208.     else {
  209.     MesgLine = ""
  210.     return 0
  211.     }
  212. }
  213.  
  214. # Read body of message.
  215. # If OneSys is true, only one line is read, just enough to confirm that
  216. # there will be a body to the message.
  217. # The returned buffer will not have a newline after the last line.
  218. function ReadBody(OneSys,  message,LineCount) {
  219.     # Read lines until EOF reached,
  220.     # or a line consisting solely of "." is entered
  221.     MesgLine = ""
  222.     if (MesgIn()) {
  223.     message = MesgLine
  224.     LineCount = 1
  225.     if (!OneSys)
  226.         while (MesgIn()) {
  227.         message = "\n" message MesgLine
  228.         LineCount++
  229.         }
  230.     if (Debug > 4)
  231.         printf "So far: %d lines in message body.\n",LineCount
  232.     }
  233.     if (!LineCount) {
  234.     print "No message body.  Message cancelled."
  235.     exit(1)
  236.     }
  237.     return message
  238. }
  239.  
  240. function GetAliases(Aliases,Verbose) {
  241.     if ("MAILRC" in ENVIRON)
  242.     ProcAliasFile(ENVIRON["MAILRC"],Aliases,Verbose)
  243.     else if ("MUSHRC" in ENVIRON)
  244.     ProcAliasFile(ENVIRON["MUSHRC"],Aliases,Verbose)
  245.     else if ("HOME" in ENVIRON)
  246.     ProcAliasFile(ENVIRON["HOME"] "/.mailrc",Aliases,Verbose)
  247. }
  248.  
  249. function ProcAliasFile(file,Aliases,Verbose,  extended,alias,lastalias) {
  250.     # mailx allows "alias" or "group"
  251.     alias = "(alias|group)"
  252.     if (Verbose)
  253.     print "Reading aliases from " file "..."
  254.     while ((getline < file) == 1) {
  255.         if (extended) {
  256.             if ($0 ~ /\\[ \t]*$/)
  257.                 sub(/[ \t]*\\[ \t]*$/," ")
  258.             else {
  259.         if (Verbose)
  260.             print lastalias ": " Aliases[lastalias] $0
  261.                 extended = 0
  262.         }
  263.             Aliases[lastalias] = Aliases[lastalias] $0
  264.         }
  265.         else if ($1 ~ "^" alias "$") {
  266.             lastalias = $2
  267.             sub("^[ \t]*" alias "[ \t]+" $2 "[ \t]+","")
  268.             if ($0 ~ /\\[ \t]*$/) {
  269.                 sub(/[ \t]*\\[ \t]*$/," ")
  270.                 extended = 1
  271.             }
  272.         else if (Verbose)
  273.         print lastalias ": " $0
  274.             Aliases[lastalias] = $0
  275.         }
  276.     }
  277.     if (Verbose)
  278.     print "Done reading aliases from " file "."
  279.     close(file)
  280. }
  281.  
  282. # print errmsg on stderr & exit
  283. function fail(errmsg) {
  284.     system("echo \"" errmsg "\" 1>&2")
  285.     exit 1
  286. }
  287.  
  288. # 91/03/13 john h. dubois iii
  289. # Sets global var "machine" to uucp name,
  290. # "domname" to internet-style address (if machine is running MMDF),
  291. # and "locname" if MLOCMACHINE is defined.
  292. function GetMailName(  mlname,mldomain,mlocmachine,proc,tailor) {
  293.     tailor = "/usr/mmdf/mmdftailor"
  294.     proc = "uuname -l"
  295.     proc | getline machine
  296.     close(proc)
  297.     if ((getline < tailor) == -1)
  298.         domname = machine
  299.     else do {
  300.     gsub(/^"|"$/,"",$2)
  301.         if ($1 == "MLNAME")
  302.             mlname = $2
  303.         else if ($1 == "MLDOMAIN")
  304.             mldomain = $2
  305.         else if ($1 == "MLOCMACHINE")
  306.             mlocmachine = $2
  307.     else
  308.         continue
  309.     if (mlname != "" && mldomain != "" && mlocmachine != "")
  310.         break
  311.     } while ((getline < tailor) == 1)
  312.     close(tailor)
  313.     if (machine == "" && domname == "")
  314.     fail("Could not get machine name or address.")
  315.     domname = mlname "." mldomain
  316.     if (mlocmachine != "")
  317.     locname = mlocmachine "." domname
  318. }
  319.  
  320. # Separates addresses by the machine they are on
  321. # Sets users[site] to contain a space-separated list of users at that site
  322. function SepMachine(Addrs,NumAddrs,Aliases,users,verbose,
  323. elem,Addr,AddrInd,AliasAddrs) {
  324.     for (AddrInd = 1; NumAddrs; AddrInd++) {
  325.     Addr = Addrs[AddrInd]
  326.     NumAddrs--
  327.     # If address contains an "@", field 1 is username, field 2 is sitename
  328.         if (Addr ~ "@") {
  329.             split(Addr,elem,"@")
  330.             users[elem[2]] = users[elem[2]] elem[1] " "
  331.         } else if (Addr ~ "!") {
  332.         # If address contains "!"s, first field is site to send mail to,
  333.         # rest of address is address to send to at site
  334.             split(Addr,elem,"!")
  335.             # remove first site and "!" that follows it from path
  336.             sub(elem[1] "!","",Addr)
  337.             users[elem[1]] = users[elem[1]] " " Addr
  338.         } else if (Addr != "-")  # Other addresses are local or aliases
  339.             if (Addr in Aliases) {
  340.                 if (verbose)
  341.                     printf "Expanding alias \"%s\" to: %s\n",Addr,Aliases[Addr]
  342.                 split(Aliases[Addr],AliasAddrs,"[ \t]+")
  343.                 SepMachine(AliasAddrs,Aliases,verbose)
  344.             }
  345.             else
  346.                 users["LOCAL"] = users["LOCAL"] " " Addr
  347.     }
  348. }
  349.  
  350. # Echo header & message into uux with command to rmail recipients
  351. # If ReadIn is true, after echoing read input & write to proc until done.
  352. function SendMessage(System,ReadIn,UseRmail,UseSubmit,AllowReturn,Immediate,
  353. TrustMe,logLevel,logFile,Header,message,users,
  354. proc,UserList,i,User,r,t,V,L) {
  355.     if (!UseSubmit && (System == "LOCAL" || System == machine || \
  356.     System == domname || System == locname))
  357.     proc = "/usr/bin/rmail " users[System]
  358.     else if (UseRmail || UseSubmit) {
  359.     split(users[System],UserList)
  360.     if (UseRmail)
  361.         proc = "/usr/bin/rmail"
  362.     else {
  363.         r = AllowReturn ? "r" : "q"
  364.         if (TrustMe)
  365.         t = "t"
  366.         if (Immediate)
  367.         l = "ln"
  368.         if (logFile)
  369.         L = "L" logFile "*"
  370.         if (logLevel)
  371.         V = "V" logLevel "*"
  372.         proc = "/usr/mmdf/bin/submit -" L t l r V "vk10*xto*"
  373.         if (Debug > 1)
  374.         proc = proc "wW"
  375.     }
  376.     for (i in UserList) {
  377.         if ((User = UserList[i]) != "")
  378.         proc = proc " " System "!" User
  379.     }
  380.     }
  381.     else
  382.         # parenthesize recipients so their !s will not be interpreted by local
  383.         # uux.  Use -r since we might want to look at the result of this
  384.     # program before it's xferred.
  385.         proc = "uux -r - " System "!rmail \\(" users[System] "\\)"
  386.     if (Debug)
  387.     printf "\nExecuting command: %s\nHeader is:\n%s",proc,Header
  388.     print Header message | proc
  389.     if (ReadIn)
  390.     while (MesgIn())
  391.         print MesgLine | proc
  392.     if (Debug)
  393.     print "Closing " proc "..."
  394.     close(proc)
  395.     if (Debug)
  396.     print "Done closing " proc "."
  397. }
  398.  
  399. ### Start of ProcArgs library
  400. # @(#) ProcArgs 1.11 96/12/08
  401. # 92/02/29 john h. dubois iii (john@armory.com)
  402. # 93/07/18 Added "#" arg type
  403. # 93/09/26 Do not count -h against MinArgs
  404. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  405. #          Removed meaning of "+" or "-" by itself.
  406. # 94/03/08 Added & option and *()< option types.
  407. # 94/04/02 Added NoRCopt to Opts()
  408. # 94/06/11 Mark numeric variables as such.
  409. # 94/07/08 Opts(): Do not require any args if h option is given.
  410. # 95/01/22 Record options given more than once.  Record option num in argv.
  411. # 95/06/08 Added ExclusiveOptions().
  412. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  413. #          Expand $VARNAME at the start of its filenames.
  414. #          Let varname=0 and -option- turn off an option.
  415. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  416. #          of the vars should be searched for in the environment.
  417. #          Check for duplicate rcfiles.
  418. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  419. #          now return various negatives values on error, not just -1, and
  420. #          Opts() may set Err to various positive values, not just 1.
  421. #          Added AllowUnrecOpt.
  422. # 96/05/23 Check type given for & option
  423. # 96/06/15 Re-port to awk
  424. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  425. #          used by other functions.
  426. # 96/10/15 Added OptChars
  427. # 96/11/01 Added exOpts arg to Opts()
  428. # 96/11/16 Added ; type
  429. # 96/12/08 Added Opt2Set() & Opt2Sets()
  430. # 96/12/27 Added CmdLineOpt()
  431.  
  432. # optlist is a string which contains all of the possible command line options.
  433. # A character followed by certain characters indicates that the option takes
  434. # an argument, with type as follows:
  435. # :    String argument
  436. # ;    Non-empty string argument
  437. # *    Floating point argument
  438. # (    Non-negative floating point argument
  439. # )    Positive floating point argument
  440. # #    Integer argument
  441. # <    Non-negative integer argument
  442. # >    Positive integer argument
  443. # The only difference the type of argument makes is in the runtime argument
  444. # error checking that is done.
  445.  
  446. # The & option is a special case used to get numeric options without the
  447. # user having to give an option character.  It is shorthand for [-+.0-9].
  448. # If & is included in optlist and an option string that begins with one of
  449. # these characters is seen, the value given to "&" will include the first
  450. # char of the option.  & must be followed by a type character other than ":"
  451. # or ";".
  452. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  453.  
  454. # Strings in argv[] which begin with "-" or "+" are taken to be
  455. # strings of options, except that a string which consists solely of "-"
  456. # or "+" is taken to be a non-option string; like other non-option strings,
  457. # it stops the scanning of argv and is left in argv[].
  458. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  459. # If an option takes an argument, the argument may either immediately
  460. # follow it or be given separately.
  461. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  462. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  463. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  464. # this feature had a flaw that caused problems in some cases.  See the OptChars
  465. # parameter to explicitly set the option-specifier characters.
  466.  
  467. # If an option that does not take an argument is given,
  468. # an index with its name is created in Options and its value is set to the
  469. # number of times it occurs in argv[].
  470.  
  471. # If an option that does take an argument is given, an index with its name is
  472. # created in Options and its value is set to the value of the argument given
  473. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  474. # If an option that takes an argument is given more than once,
  475. # Options[option-name,"count"] is incremented, and the value is assigned to
  476. # the index (option-name,instance) where instance is 2 for the second occurance
  477. # of the option, etc.
  478. # In other words, the first time an option with a value is encountered, the
  479. # value is assigned to an index consisting only of its name; for any further
  480. # occurances of the option, the value index has an extra (count) dimension.
  481.  
  482. # The sequence number for each option found in argv[] is stored in
  483. # Options[option-name,"num",instance], where instance is 1 for the first
  484. # occurance of the option, etc.  The sequence number starts at 1 and is
  485. # incremented for each option, both those that have a value and those that
  486. # do not.  Options set from a config file have a value of 0 assigned to this.
  487.  
  488. # Options and their arguments are deleted from argv.
  489. # Note that this means that there may be gaps left in the indices of argv[].
  490. # If compress is nonzero, argv[] is packed by moving its elements so that
  491. # they have contiguous integer indices starting with 0.
  492. # Option processing will stop with the first unrecognized option, just as
  493. # though -- was given except that unlike -- the unrecognized option will not be
  494. # removed from ARGV[].  Normally, an error value is returned in this case.
  495. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  496. # be found, so the number of remaining arguments is returned instead.
  497. # If OptChars is not a null string, it is the set of characters that indicate
  498. # that an argument is an option string if the string begins with one of the
  499. # characters.  A string consisting solely of two of the same option-indicator
  500. # characters stops the scanning of argv[].  The default is "-+".
  501. # argv[0] is not examined.
  502. # The number of arguments left in argc is returned.
  503. # If an error occurs, the global string OptErr is set to an error message
  504. # and a negative value is returned.
  505. # Current error values:
  506. # -1: option that required an argument did not get it.
  507. # -2: argument of incorrect type supplied for an option.
  508. # -3: unrecognized (invalid) option.
  509. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  510. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  511. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  512. {
  513. # ArgNum is the index of the argument being processed.
  514. # ArgsLeft is the number of arguments left in argv.
  515. # Arg is the argument being processed.
  516. # ArgLen is the length of the argument being processed.
  517. # ArgInd is the position of the character in Arg being processed.
  518. # Option is the character in Arg being processed.
  519. # Pos is the position in OptList of the option being processed.
  520. # NumOpt is true if a numeric option may be given.
  521.     ArgsLeft = argc
  522.     NumOpt = index(OptList,"&")
  523.     OptionNum = 0
  524.     if (OptChars == "")
  525.     OptChars = "-+"
  526.     while (OptChars != "") {
  527.     c = substr(OptChars,1,1)
  528.     OptChars = substr(OptChars,2)
  529.     OptCharSet[c]
  530.     OptTerm[c c]
  531.     }
  532.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  533.     Arg = argv[ArgNum]
  534.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  535.         break    # Not an option; quit
  536.     if (Arg in OptTerm) {
  537.         delete argv[ArgNum]
  538.         ArgsLeft--
  539.         break
  540.     }
  541.     ArgLen = length(Arg)
  542.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  543.         Option = substr(Arg,ArgInd,1)
  544.         if (NumOpt && Option ~ /[-+.0-9]/) {
  545.         # If this option is a numeric option, make its flag be & and
  546.         # its option string flag position be the position of & in
  547.         # the option string.
  548.         Option = "&"
  549.         Pos = NumOpt
  550.         # Prefix Arg with a char so that ArgInd will point to the
  551.         # first char of the numeric option.
  552.         Arg = "&" Arg
  553.         ArgLen++
  554.         }
  555.         # Find position of flag in option string, to get its type (if any).
  556.         # Disallow & as literal flag.
  557.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  558.         if (AllowUnrecOpt) {
  559.             Escape = 1
  560.             break
  561.         }
  562.         else {
  563.             OptErr = "Invalid option: " specGiven Option
  564.             return -3
  565.         }
  566.         }
  567.  
  568.         # Find what the value of the option will be if it takes one.
  569.         # NeedNextOpt is true if the option specifier is the last char of
  570.         # this arg, which means that if the option requires a value it is
  571.         # the next arg.
  572.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  573.         if (GotValue = ArgNum + 1 < argc)
  574.             Value = argv[ArgNum+1]
  575.         }
  576.         else {    # Value is included with option
  577.         Value = substr(Arg,ArgInd + 1)
  578.         GotValue = 1
  579.         }
  580.  
  581.         if (HadValue = AssignVal(Option,Value,Options,
  582.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  583.         specGiven)) {
  584.         if (HadValue < 0)    # error occured
  585.             return HadValue
  586.         if (HadValue == 2)
  587.             ArgInd++    # Account for the single-char value we used.
  588.         else {
  589.             if (NeedNextOpt) {    # option took next arg as value
  590.             delete argv[++ArgNum]
  591.             ArgsLeft--
  592.             }
  593.             break    # This option has been used up
  594.         }
  595.         }
  596.     }
  597.     if (Escape)
  598.         break
  599.     # Do not delete arg until after processing of it, so that if it is not
  600.     # recognized it can be left in ARGV[].
  601.     delete argv[ArgNum]
  602.     ArgsLeft--
  603.     }
  604.     if (compress != 0) {
  605.     dest = 1
  606.     src = argc - ArgsLeft + 1
  607.     for (count = ArgsLeft - 1; count; count--) {
  608.         ARGV[dest] = ARGV[src]
  609.         dest++
  610.         src++
  611.     }
  612.     }
  613.     return ArgsLeft
  614. }
  615.  
  616. # Assignment to values in Options[] occurs only in this function.
  617. # Option: Option specifier character.
  618. # Value: Value to be assigned to option, if it takes a value.
  619. # Options[]: Options array to return values in.
  620. # ArgType: Argument type specifier character.
  621. # GotValue: Whether any value is available to be assigned to this option.
  622. # Name: Name of option being processed.
  623. # OptionNum: Number of this option (starting with 1) if set in argv[],
  624. #     or 0 if it was given in a config file or in the environment.
  625. # SingleOpt: true if the value (if any) that is available for this option was
  626. #     given as part of the same command line arg as the option.  Used only for
  627. #     options from the command line.
  628. # specGiven is the option specifier character use, if any (e.g. - or +),
  629. # for use in error messages.
  630. # Global variables: OptErr
  631. # Return value: negative value on error, 0 if option did not require an
  632. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  633. # the arg.
  634. # Current error values:
  635. # -1: Option that required an argument did not get it.
  636. # -2: Value of incorrect type supplied for option.
  637. # -3: Bad type given for option &
  638. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  639. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  640.     # If option takes a value...    [
  641.     NumTypes = "*()#<>]"
  642.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  643.     OptErr = "Bad type given for & option"
  644.     return -3
  645.     }
  646.  
  647.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  648.     if (!GotValue) {
  649.         if (Name != "")
  650.         OptErr = "Variable requires a value -- " Name
  651.         else
  652.         OptErr = "option requires an argument -- " Option
  653.         return -1
  654.     }
  655.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  656.         OptErr = Err
  657.         return -2
  658.     }
  659.     # Mark this as a numeric variable; will be propogated to Options[] val.
  660.     if (ArgType != ":" && ArgType != ";")
  661.         Value += 0
  662.     if ((Instance = ++Options[Option,"count"]) > 1)
  663.         Options[Option,Instance] = Value
  664.     else
  665.         Options[Option] = Value
  666.     }
  667.     # If this is an environ or rcfile assignment & it was given a value...
  668.     else if (!OptionNum && Value != "") {
  669.     UsedValue = 1
  670.     # If the value is "0" or "-" and this is the first instance of it,
  671.     # do not set Options[Option]; this allows an assignment in an rcfile to
  672.     # turn off an option (for the simple "Option in Options" test) in such
  673.     # a way that it cannot be turned on in a later file.
  674.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  675.         Instance = 1
  676.     else
  677.         Instance = ++Options[Option]
  678.     # Save the value even though this is a flag
  679.     Options[Option,Instance] = Value
  680.     }
  681.     # If this is a command line flag and has a - following it in the same arg,
  682.     # it is being turned off.
  683.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  684.     UsedValue = 2
  685.     if (Option in Options)
  686.         Instance = ++Options[Option]
  687.     else
  688.         Instance = 1
  689.     Options[Option,Instance]
  690.     }
  691.     # If this is a flag assignment without a value, increment the count for the
  692.     # flag unless it was turned off.  The indicator for a flag being turned off
  693.     # is that the flag index has not been set in Options[] but it has an
  694.     # instance count.
  695.     else if (Option in Options || !((Option,1) in Options))
  696.     # Increment number of times this flag seen; will inc null value to 1
  697.     Instance = ++Options[Option]
  698.     Options[Option,"num",Instance] = OptionNum
  699.     return UsedValue
  700. }
  701.  
  702. # Option is the option letter
  703. # Value is the value being assigned
  704. # Name is the var name of the option, if any
  705. # ArgType is one of:
  706. # :    String argument
  707. # ;    Non-null string argument
  708. # *    Floating point argument
  709. # (    Non-negative floating point argument
  710. # )    Positive floating point argument
  711. # #    Integer argument
  712. # <    Non-negative integer argument
  713. # >    Positive integer argument
  714. # specGiven is the option specifier character use, if any (e.g. - or +),
  715. # for use in error messages.
  716. # Returns null on success, err string on error
  717. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  718.     if (ArgType == ":")
  719.     return ""
  720.     if (ArgType == ";") {
  721.     if (Value == "")
  722.         Err = "must be a non-empty string"
  723.     }
  724.     # A number begins with optional + or -, and is followed by a string of
  725.     # digits or a decimal with digits before it, after it, or both
  726.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  727.     Err = "must be a number"
  728.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  729.     Err = "may not include a fraction"
  730.     else if (ArgType ~ "[()<>]" && Value < 0)
  731.     Err = "may not be negative"
  732.     # (
  733.     else if (ArgType ~ "[)>]" && Value == 0)
  734.     Err = "must be a positive number"
  735.     if (Err != "") {
  736.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  737.     if (Name != "")
  738.         return ErrStr "variable " substr(Name,1,1) " " Err
  739.     else {
  740.         if (Option == "&")
  741.         Option = Value
  742.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  743.     }
  744.     }
  745.     else
  746.     return ""
  747. }
  748.  
  749. # Note: only the above functions are needed by ProcArgs.
  750. # The rest of these functions call ProcArgs() and also do other
  751. # option-processing stuff.
  752.  
  753. # Opts: Process command line arguments.
  754. # Opts processes command line arguments using ProcArgs()
  755. # and checks for errors.  If an error occurs, a message is printed
  756. # and the program is exited.
  757. #
  758. # Input variables:
  759. # Name is the name of the program, for error messages.
  760. # Usage is a usage message, for error messages.
  761. # OptList the option description string, as used by ProcArgs().
  762. # MinArgs is the minimum number of non-option arguments that this
  763. # program should have, non including ARGV[0] and +h.
  764. # If the program does not require any non-option arguments,
  765. # MinArgs should be omitted or given as 0.
  766. # rcFiles, if given, is a colon-seprated list of filenames to read for
  767. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  768. # by the value of the environment variable HOME.  If a filename begins with
  769. # $, the part from the character after the $ up until (but not including)
  770. # the first character not in [a-zA-Z0-9_] will be searched for in the
  771. # environment; if found its value will be substituted, if not the filename will
  772. # be discarded.
  773. # rcfiles are read in the order given.
  774. # Values given in them will not override values given on the command line,
  775. # and values given in later files will not override those set in earlier
  776. # files, because AssignVal() will store each with a different instance index.
  777. # The first instance of each variable, either on the command line or in an
  778. # rcfile, will be stored with no instance index, and this is the value
  779. # normally used by programs that call this function.
  780. # VarNames is a comma-separated list of variable names to map to options,
  781. # in the same order as the options are given in OptList.
  782. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  783. # searched for in the environment.  If set to -1, all values will be searched
  784. # for in the environment.  Values given in the environment will override
  785. # those given in the rcfiles but not those given on the command line.
  786. # NoRCopt, if given, is an additional letter option that if given on the
  787. # command line prevents the rcfiles from being read.
  788. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  789. # ExclusiveOptions() for a description of exOpts.
  790. # Special options:
  791. # If x is made an option and is given, some debugging info is output.
  792. # h is assumed to be the help option.
  793.  
  794. # Global variables:
  795. # The command line arguments are taken from ARGV[].
  796. # The arguments that are option specifiers and values are removed from
  797. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  798. # The number of elements in ARGV[] should be in ARGC.
  799. # After processing, ARGC is set to the number of elements left in ARGV[].
  800. # The option values are put in Options[].
  801. # On error, Err is set to a positive integer value so it can be checked for in
  802. # an END block.
  803. # Return value: The number of elements left in ARGV is returned.
  804. # Must keep OptErr global since it may be set by InitOpts().
  805. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  806. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  807.     if (MinArgs == "")
  808.     MinArgs = 0
  809.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  810.     optChars)
  811.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  812.     if (ArgsLeft >= 0) {
  813.         OptErr = "Not enough arguments"
  814.         Err = 4
  815.     }
  816.     else
  817.         Err = -ArgsLeft
  818.     printf "%s: %s.\nUse -h for help.\n%s\n",
  819.     Name,OptErr,Usage > "/dev/stderr"
  820.     exit 1
  821.     }
  822.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  823.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  824.     {
  825.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  826.     Err = -e
  827.     exit 1
  828.     }
  829.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  830.     {
  831.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  832.     Err = 1
  833.     exit 1
  834.     }
  835.     return ArgsLeft
  836. }
  837.  
  838. # ReadConfFile(): Read a file containing var/value assignments, in the form
  839. # <variable-name><assignment-char><value>.
  840. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  841. # line and whitespace between the variable name and the assignment character) 
  842. # is stripped.  Lines that do not contain an assignment operator or which
  843. # contain a null variable name are ignored, other than possibly being noted in
  844. # the return value.  If more than one assignment is made to a variable, the
  845. # first assignment is used.
  846. # Input variables:
  847. # File is the file to read.
  848. # Comment is the line-comment character.  If it is found as the first non-
  849. #     whitespace character on a line, the line is ignored.
  850. # Assign is the assignment string.  The first instance of Assign on a line
  851. #     separates the variable name from its value.
  852. # If StripWhite is true, whitespace around the value (whitespace between the
  853. #     assignment char and trailing whitespace on the line) is stripped.
  854. # VarPat is a pattern that variable names must match.  
  855. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  856. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  857. #     a line; no assignment operator is needed.  These variables are set in
  858. #     the output array with a null value.  Lines containing nothing but
  859. #     whitespace are still ignored.
  860. # Output variables:
  861. # Values[] contains the assignments, with the indexes being the variable names
  862. #     and the values being the assigned values.
  863. # Lines[] contains the line number that each variable occured on.  A flag set
  864. #     is record by giving it an index in Lines[] but not in Values[].
  865. # Return value:
  866. # If any errors occur, a string consisting of descriptions of the errors
  867. # separated by newlines is returned.  In no case will the string start with a
  868. # numeric value.  If no errors occur,  the number of lines read is returned.
  869. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  870. FlagsOK,
  871. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  872.     if (Comment != "")
  873.     Comment = "^" Comment
  874.     AssignLen = length(Assign)
  875.     if (VarPat == "")
  876.     VarPat = "."    # null varname not allowed
  877.     while ((Status = (getline Line < File)) == 1) {
  878.     LineNum++
  879.     sub("^[ \t]+","",Line)
  880.     if (Line == "")        # blank line
  881.         continue
  882.     if (Comment != "" && Line ~ Comment)
  883.         continue
  884.     if (Pos = index(Line,Assign)) {
  885.         Var = substr(Line,1,Pos-1)
  886.         Val = substr(Line,Pos+AssignLen)
  887.         if (StripWhite) {
  888.         sub("^[ \t]+","",Val)
  889.         sub("[ \t]+$","",Val)
  890.         }
  891.     }
  892.     else {
  893.         Var = Line    # If no value, var is entire line
  894.         Val = ""
  895.     }
  896.     if (!FlagsOK && Val == "") {
  897.         Errs = Errs \
  898.         sprintf("\nBad assignment on line %d of file %s: %s",
  899.         LineNum,File,Line)
  900.         continue
  901.     }
  902.     sub("[ \t]+$","",Var)
  903.     if (Var !~ VarPat) {
  904.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  905.         LineNum,File,Var)
  906.         continue
  907.     }
  908.     if (!(Var in Lines)) {
  909.         Lines[Var] = LineNum
  910.         if (Pos)
  911.         Values[Var] = Val
  912.     }
  913.     }
  914.     if (Status)
  915.     Errs = Errs "\nCould not read file " File
  916.     close(File)
  917.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  918. }
  919.  
  920. # Variables:
  921. # Data is stored in Options[].
  922. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  923. # Global vars:
  924. # Sets OptErr.  Uses ENVIRON[].
  925. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  926. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  927. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  928. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  929.     split("",filesRead,"")    # make awk know this is an array
  930.     NumVars = split(VarNames,Vars,",")
  931.     TypesInd = Ret = 0
  932.     if (EnvSearch == -1)
  933.     EnvSearch = NumVars
  934.     for (i = 1; i <= NumVars; i++) {
  935.     Var = Vars[i]
  936.     CharOpt = substr(OptList,++TypesInd,1)
  937.     if (CharOpt ~ "^[:;*()#<>&]$")
  938.         CharOpt = substr(OptList,++TypesInd,1)
  939.     Map[Var] = CharOpt
  940.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  941.     # Do not overwrite entries from environment
  942.     if (i <= EnvSearch && Var in ENVIRON &&
  943.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  944.         return Err
  945.     }
  946.  
  947.     numrcFiles = split(rcFiles,fNames,":")
  948.     for (i = 1; i <= numrcFiles; i++) {
  949.     rcFile = fNames[i]
  950.     if (rcFile ~ "^~/")
  951.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  952.     else if (rcFile ~ /^\$/) {
  953.         rcFile = substr(rcFile,2)
  954.         match(rcFile,"^[a-zA-Z0-9_]*")
  955.         envvar = substr(rcFile,1,RLENGTH)
  956.         if (envvar in ENVIRON)
  957.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  958.         else
  959.         continue
  960.     }
  961.     if (rcFile in filesRead)
  962.         continue
  963.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  964.     # may be the same
  965.     filesRead[rcFile]
  966.     if ("x" in Options)
  967.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  968.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  969.     if (retStr > 0)
  970.         READ_RCFILE = 1
  971.     else if (ret != "") {
  972.         OptErr = retStr
  973.         Ret = -1
  974.     }
  975.     for (Var in Lines)
  976.         if (Var in Map) {
  977.         if ((Err = AssignVal(Map[Var],
  978.         Var in Values ? Values[Var] : "",Options,Types[Var],
  979.         Var in Values,Var,0)) < 0)
  980.             return Err
  981.         }
  982.         else {
  983.         OptErr = sprintf(\
  984.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  985.         Lines[Var],rcFile)
  986.         Ret = -1
  987.         }
  988.     }
  989.  
  990.     if ("x" in Options)
  991.     for (Var in Map)
  992.         if (Map[Var] in Options)
  993.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  994.         "/dev/stderr"
  995.         else
  996.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  997.     return Ret
  998. }
  999.  
  1000. # OptSets is a semicolon-separated list of sets of option sets.
  1001. # Within a list of option sets, the option sets are separated by commas.  For
  1002. # each set of sets, if any option in one of the sets is in Options[] AND any
  1003. # option in one of the other sets is in Options[], an error string is returned.
  1004. # If no conflicts are found, nothing is returned.
  1005. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1006. # the exclusions presented by the first set of sets (ab,def,g) if:
  1007. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1008. # (a or b is in Options[]) AND (g is in Options) OR
  1009. # (d, e, or f is in Options[]) AND (g is in Options)
  1010. # An error will be returned due to the exclusions presented by the second set
  1011. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1012. # todo: make options given on command line unset options given in config file
  1013. # todo: that they conflict with.
  1014. function ExclusiveOptions(OptSets,Options,
  1015. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1016. SetNum,OSetNum) {
  1017.     NumSetSets = split(OptSets,SetSets,";")
  1018.     # For each set of sets...
  1019.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1020.     # NumSets is the number of sets in this set of sets.
  1021.     NumSets = split(SetSets[SetSet],Sets,",")
  1022.     # For each set in a set of sets except the last...
  1023.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1024.         s1 = Sets[SetNum]
  1025.         L1 = length(s1)
  1026.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1027.         # If any of the options in this set was given, check whether
  1028.         # any of the options in the other sets was given.  Only check
  1029.         # later sets since earlier sets will have already been checked
  1030.         # against this set.
  1031.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1032.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1033.             s2 = Sets[OSetNum]
  1034.             L2 = length(s2)
  1035.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1036.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1037.                 ErrStr = ErrStr "\n"\
  1038.                 sprintf("Cannot give both %s and %s options.",
  1039.                 c1,c2)
  1040.             }
  1041.     }
  1042.     }
  1043.     if (ErrStr != "")
  1044.     return substr(ErrStr,2)
  1045.     return ""
  1046. }
  1047.  
  1048. # The value of each instance of option Opt that occurs in Options[] is made an
  1049. # index of Set[].
  1050. # The return value is the number of instances of Opt in Options.
  1051. function Opt2Set(Options,Opt,Set,  count) {
  1052.     if (!(Opt in Options))
  1053.     return 0
  1054.     Set[Options[Opt]]
  1055.     count = Options[Opt,"count"]
  1056.     for (; count > 1; count--)
  1057.     Set[Options[Opt,count]]
  1058.     return count
  1059. }
  1060.  
  1061. # The value of each instance of option Opt that occurs in Options[] that
  1062. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1063. # Other values are made indexes of Set[].
  1064. # The return value is the number of instances of Opt in Options.
  1065. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1066.     ret = Opt2Set(Options,Opt,aSet)
  1067.     for (value in aSet)
  1068.     if (substr(value,1,1) == "!")
  1069.         nSet[substr(value,2)]
  1070.     else
  1071.         Set[value]
  1072.     return ret
  1073. }
  1074.  
  1075. # Returns true if option Opt was given on the command line.
  1076. function CmdLineOpt(Options,Opt,  i) {
  1077.     for (i = 1; (Opt,"num",i) in Options; i++)
  1078.     if (Options[Opt,"num",i] != 0)
  1079.         return 1
  1080.     return 0
  1081. }
  1082. ### End of ProcArgs library
  1083. ### Begin set library
  1084. # 96/05/23 added return values  jhdiii
  1085. # 96/05/25 added set2list()
  1086. # 97/01/26 Added AOnly(), Exclusive()
  1087.  
  1088. # Return value: the number of new elements added to Inter
  1089. function Intersection(A,B,Inter,  Elem,Count) {
  1090.     for (Elem in A)
  1091.     if (Elem in B && !(Elem in Inter)) {
  1092.         Inter[Elem]
  1093.         Count++
  1094.     }
  1095.     return Count
  1096. }
  1097.  
  1098. # Any element that is in A or B but not both and which is not already in
  1099. # Excl is added to Excl.
  1100. # Return value: the number of new elements added to Excl
  1101. function Exclusive(A,B,Excl) {
  1102.     return AOnly(A,B,Excl) + AOnly(B,A,Excl)
  1103. }
  1104.  
  1105. # Any element that is in A and not in B or aOnly is added to aOnly.
  1106. # Return value: the number of new elements added to aOnly.
  1107. function AOnly(A,B,aOnly,  Elem,Count) {
  1108.     for (Elem in A)
  1109.     if (!(Elem in B) && !(Elem in aOnly)) {
  1110.         aOnly[Elem]
  1111.         Count++
  1112.     }
  1113.     return Count
  1114. }
  1115.  
  1116. # Return value: the number of new elements added to Both
  1117. function Union(A,B,Both) {
  1118.     return CopySet(A,Both) + CopySet(B,Both)
  1119. }
  1120.  
  1121. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1122. # Return value: the number of elements deleted.
  1123. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1124.     for (Elem in Subtrahend)
  1125.     if (Elem in Minuend) {
  1126.         delete Minuend[Elem]
  1127.         nDel++
  1128.     }
  1129.     return nDel
  1130. }
  1131.  
  1132. # Return value: the number of new elements added to To
  1133. function CopySet(From,To,  Elem,n) {
  1134.     for (Elem in From)
  1135.     if (!(Elem in To)) {
  1136.         To[Elem]
  1137.         n++
  1138.     }
  1139.     return n
  1140. }
  1141.  
  1142. # Returns 1 if Set is empty, 0 if not.
  1143. function IsEmpty(Set,  i) {
  1144.     for (i in Set)
  1145.     return 0
  1146.     return 1
  1147. }
  1148.  
  1149. # MakeSet: make a set from a list.
  1150. # An index with the name of each element of the list is created in the given
  1151. # array.
  1152. # Input variables:
  1153. # Elements is a string containing the list of elements.
  1154. # Sep is the character that separates the elements of the list.
  1155. # Output variables:
  1156. # Set is the array.
  1157. # Return value: the number of new elements added to the set.
  1158. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1159.     nFound = 0
  1160.     Num = split(Elements,Names,Sep)
  1161.     for (i = 1; i <= Num; i++) {
  1162.     ind = Names[i]
  1163.     if (!(ind in Set)) {
  1164.         Set[ind]
  1165.         nFound++
  1166.     }
  1167.     }
  1168.     return nFound
  1169. }
  1170.  
  1171. # Returns the number of elements in set Set
  1172. function NumElem(Set,  elem,Num) {
  1173.     for (elem in Set)
  1174.     Num++
  1175.     return Num
  1176. }
  1177.  
  1178. # Remove all elements from Set
  1179. function DeleteAll(Set,  i) {
  1180.     split("",Set,",")
  1181. }
  1182.  
  1183. # Returns a list of all of the elements in Set[], with each pair of elements
  1184. # separated by Sep.
  1185. function set2list(Set,Sep,  list,elem) {
  1186.     for (elem in Set)
  1187.     list = list Sep elem
  1188.     return substr(list,2)    # skip 1st separator
  1189. }
  1190. ### End set library
  1191.