#!/usr/local/bin/gawk -f # @(#) fixactive.gawk 1.2 94/10/15 # 94/07/25 john h. dubois iii # 94/08/04 Added all options. # 94/09/29 More checks on validity of input. # 94/10/15 Added s option. BEGIN { Name = "fixactive" Usage = "Usage: " Name " [-hnq] [-s] template-file [active-file]" ARGC = Opts(Name,Usage,"hnqs:",1) if ("h" in Options) { printf \ "%s: fix active file by comparing it to a template file.\n"\ "%s\n"\ "Any groups in template-file that are not in the active file are added,\n"\ "with the same status as they have in the template file.\n"\ "Any groups in the active file that are not in template-file are removed.\n"\ "The new active file is written to the standard output.\n"\ "Options:\n"\ "-h: Print this help.\n"\ "-n: Do not delete any groups from the active file even if they aren't\n"\ " in template-file.\n"\ "-s: Don't delete groups that match the given pattern even if they\n"\ " aren't in template-file. This can be used to preserve local groups.\n"\ "-q: Do not print notice line for each line added to the active file.\n", Name,Usage exit } NoDel = "n" in Options Quiet = "q" in Options if ("s" in Options) Save = Options["s"] compare = ARGV[1] if (ARGC > 2) local = ARGV[2] else local = "/usr/lib/news/active" while ((ret = (getline < compare)) == 1) { sub("\015$","") # lines sometimes end in cr if (NF != 4) printf "Error in template-file: Not enough fields:\n%s\n", $0 > "/dev/stderr" else if ($2 !~ /^[0-9]+$/ || $3 !~ /^[0-9]+$/) # Check this even though it won't be used because it indicates # a garbage line. printf "Error in template-file: Bad article range:\n%s\n", $0 > "/dev/stderr" else if ($4 !~ /^([mynx]$|xm$|=)/) printf "Error in template-file: Bad status:\n%s\n", $0 > "/dev/stderr" else CompGrps[$1] = $0 } if (ret) { printf "Error reading template-file '%s'. Exiting.\n",compare > \ "/dev/stderr" exit 1 } close(compare) while ((ret = (getline < local)) == 1) { if (NF == 4) { if ($1 in CompGrps || NoDel) { print $0 delete CompGrps[$1] } else if (Save != "" && $1 ~ Save) { print $0 delete CompGrps[$1] printf "Preserved group '%s'.\n",$1 | "cat 1>&2" } else printf "%s did not exist in template-file. Deleted.\n", $1 | "cat 1>&2" } else printf "Error in active file: Not enough fields:\n%s\n", $0 > "/dev/stderr" } if (ret) { printf "Error reading active file '%s'. Exiting.\n",local > \ "/dev/stderr" exit 1 } for (Group in CompGrps) { if (!Quiet) printf "%s did not exist in local file. Added.\n", Group > "/dev/stderr" split(CompGrps[Group],F) printf "%s %s %s %s\n",Group,"0000000000","00001",F[4] NumAdded++ } printf "%d groups added from template-file.\n",NumAdded > "/dev/stderr" } # @(#) ProcArgs 1.2.1 94/06/11 # 92/02/29 john h. dubois iii (john@armory.com) # 93/07/18 Added "#" arg type # 93/09/26 Do not count -h against MinArgs # 94/01/01 Stop scanning at first non-option arg. Added ">" option type. # Removed meaning of "+" or "-" by itself. # 94/03/08 Added & option and *()< option types. # 94/04/02 Added NoRCopt to Opts() # 94/06/11 Mark numeric variables as such. # 94/07/08 Opts(): Don't require any args if 'h' option is given. # optlist is a string which contains all of the possible command line options. # A character followed by certain characters indicates that the option takes # an argument, with type as follows: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # The only difference the type of argument makes is in the runtime argument # error checking that is done. # The & option is a special case used to get numeric options without the # user having to give an option character. It is shorthand for [-+.0-9]. # If & is included in optlist and an option string that begins with one of # these characters is seen, the value given to "&" will include the first # char of the option. & must be followed by a type character other than ":". # Note that if e.g. &> is given, an option of -.5 will produce an error. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" # or "+" is taken to be a non-option string; like other non-option strings, # it stops the scanning of argv and is left in argv[]. # An argument of "--" or "++" also stops the scanning of argv[] but is removed. # If an option takes an argument, the argument may either immediately # follow it or be given separately. # "-" and "+" options are treated the same. "+" is allowed because most awks # take any -options to be arguments to themselves. gawk 2.15 was enhanced to # stop scanning when it encounters an unrecognized option, though until 2.15.5 # this feature had a bug that caused problems in some cases. # If an option that does not take an argument is given, # an index with its name is created in Options and its value is set to "1". # If an option that does take an argument is given, # an index with its name is created in Options and its value # is set to the value of the argument given for it. # Options and their arguments are deleted from argv. # Note that this means that there may be gaps left in the indices of argv[]. # If compress is nonzero, argv[] is packed by moving its elements so that # they have contiguous integer indices starting with 0. # argv[0] is not examined. # The number of arguments left in argc is returned. # If an error occurs, the global string OptErr is set to an error message # and -1 is returned. function ProcArgs(argc,argv,OptList,Options,compress, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue, NeedNextOpt) { # ArgNum is the index of the argument being processed. # ArgsLeft is the number of arguments left in argv. # Arg is the argument being processed. # ArgLen is the length of the argument being processed. # ArgInd is the position of the character in Arg being processed. # Option is the character in Arg being processed. # Pos is the position in OptList of the option being processed. # NumOpt is true if a numeric option may be given. ArgsLeft = argc NumOpt = index(OptList,"&") for (ArgNum = 1; ArgNum < argc; ArgNum++) { if ((Arg = argv[ArgNum]) !~ /^[-+]./) # Not an option; quit break delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) if (NumOpt && Option ~ /[-+.0-9]/) { Option = "&" Arg = "&" Arg ArgLen++ Pos = NumOpt } else if (!(Pos = index(OptList,Option)) || Option == "&") { OptErr = "Invalid option: -" Option return -1 } # Find what the value of the option will be if it needs one if (NeedNextOpt = (ArgInd >= ArgLen)) # Value is the next arg Value = argv[ArgNum+1] else # Value is included with option Value = substr(Arg,ArgInd + 1) if (HadValue = AssignVal(Option,Value,Options, substr(OptList,Pos + 1,1),ArgNum < (argc - 1))) { if (HadValue == -1) return -1 if (NeedNextOpt) { delete argv[++ArgNum] ArgsLeft-- } break # Used up this option } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # Global variables: OptErr # Return value: -1 on error, 0 if option did not require an argument, # 1 if it did. function AssignVal(Option,Value,Options,ArgType,GotValue,Name, UsedValue,Err) { # If option takes a value... if (UsedValue = (ArgType ~ "[:*()#<>]")) { if (!GotValue) { if (Name != "") OptErr = "Variable requires a value -- " Name else OptErr = "option requires an argument -- " Option return -1 } if ((Err = CheckType(ArgType,Value,Option,Name)) != "") { OptErr = Err return -1 } # Mark this as a numeric variable; will be propogated to Options[] val. if (ArgType != ":") Value += 0 } else Value = 1 if (!(Option in Options)) # Do not overwrite previously assigned values Options[Option] = Value return UsedValue } # Option is the option letter # Value is the value being assigned # Name is the var name of the option, if any # ArgType is one of: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # Returns null on success, err string on error function CheckType(ArgType,Value,Option,Name, Err) { if (ArgType == ":") return "" # A number begins with option + or -, and is followed by a string of # digits or a decimal with digits before it, after it, or both if (Value !~ /^[-+]?([0-9]+|[0-9]+?\.[0-9]+|[0-9]+\.)$/) Err = "must be a number" else if (ArgType ~ "[#<>]" && Value ~ /\./) Err = "may not include a fraction" else if (ArgType ~ "[()<>]" && Value < 0) Err = "may not be negative" else if (ArgType ~ "[)>]" && Value == 0) Err = "must be a positive number" if (Err != "") { if (Name != "") return "Value assigned to variable " Name " " Err else { if (Option == "&") Option = Value return "Value assigned to option -" Option " " Err } } else return "" } # Packs Arr to indices starting with 0 # Num should be the number of elements in Arr function PackArr(Arr,Num, NewInd,OldInd) { NewInd = OldInd = 0 for (; Num; Num--) { while (!(OldInd in Arr)) OldInd++ if (NewInd != OldInd) { Arr[NewInd] = Arr[OldInd] delete Arr[OldInd] } OldInd++ NewInd++ } } # Note: only the above functions are needed by ProcArgs. # The rest of these functions call ProcArgs() and also do other # option-processing stuff. # Opts: Process command line arguments. # Opts processes command line arguments using ProcArgs() # and checks for errors. If an error occurs, a message is printed # and the program is exited. # # Input variables: # Name is the name of the program, for error messages. # Usage is a usage message, for error messages. # OptList the option description string, as used by ProcArgs(). # MinArgs is the minimum number of non-option arguments that this # program should have, non including ARGV[0] and +h. # If the program does not require any non-option arguments, # MinArgs should be omitted or given as 0. # rcFile, if given, is the name of a file to read for variable initialization. # Values given in it will not override values given on the command line. # VarNames is a comma-separated list of variable names to map to options, # in the same order as the options are given in OptList. # If UseEnv is given and nonzero, the variables will also be searched for in # the environment. Values given in the environment will override those given # in the rcfile but not those given on the command line. # NoRCopt, if given, is an additional letter option that if given on the # command line prevents the rcfile from being read. # Special options: # If x is made an option and is given, some debugging info is output. # h is assumed to be the help option. # Global variables: # The command line arguments are taken from ARGV[]. # The arguments that are option specifiers and values are removed from # ARGV[], leaving only ARGV[0] and the non-option arguments. # The number of elements in ARGV[] should be in ARGC. # After processing, ARGC is set to the number of elements left in ARGV[]. # The option values are put in Options[]. # On error, Err is set to 1 so it can be checked for in an END block. # Return value: The number of elements left in ARGV is returned. function Opts(Name,Usage,OptList,MinArgs,rcFile,VarNames,UseEnv,NoRCopt, ArgsLeft) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1) # if ((ArgsLeft + ("h" in Options)) < (MinArgs+1)) { if (ArgsLeft < (MinArgs+1) && !("h" in Options)) { if (ArgsLeft != -1) OptErr = "Not enough arguments" print Name ": " OptErr ". Use -h for help." print Usage Err = 1 exit 1 } if (rcFile != "" && (NoRCopt == "" || !(NoRCopt in Options)) && InitOpts(rcFile,Options,OptList,VarNames,UseEnv) == -1) { print Name ": " OptErr ". Use -h for help." Err = 1 exit 1 } return ArgsLeft } # Global vars: sets OptErr; uses ENVIRON[]; # if anything is read from the rcFile, sets READ_RCFILE to 1 function InitOpts(rcFile,Options,OptTypes,VarNames,UseEnv, Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret) { NumVars = split(VarNames,Vars,",") TypesInd = Ret = 0 for (i = 1; i <= NumVars; i++) { Var = Vars[i] CharOpt = substr(OptTypes,++TypesInd,1) if (CharOpt ~ "^[:*()#<>&]$") CharOpt = substr(OptTypes,++TypesInd,1) Map[Var] = CharOpt Types[Var] = Type = substr(OptTypes,TypesInd+1,1) # Do not overwrite entries from environment if (UseEnv && Var in ENVIRON && AssignVal(CharOpt,ENVIRON[Var],Options, Type,1,Var) == -1) return -1 } if (rcFile ~ "^~/") rcFile = ENVIRON["HOME"] substr(rcFile,2) while ((getline Line < rcFile) == 1) { READ_RCFILE = 1 if (Line !~ /^#/ && Line !~ "^[ \t]*$") { if (Pos = index(Line,"=")) Var = substr(Line,1,Pos-1) else Var = Line # If no value, var is entire line if (Var in Map) { if (AssignVal(Map[Var],substr(Line,Pos+1),Options, Types[Var],Pos != 0,Var) == -1) return -1 } else { OptErr = sprintf("Unknown var \"%s\" set in %s",Var,rcFile) Ret = -1 } } } if ("x" in Options) for (Var in Map) if (Map[Var] in Options) printf "%s=%s\n",Var,Options[Map[Var]] else printf "%s not set\n",Var return Ret }