#!/usr/local/bin/gawk -f #!/usr/bin/awk -f # @(#) libupdate.awk 1.0 96/11/04 # 96/11/04 john h. dubois iii (john@armory.com) # Use of @(\#) is to prevent these strings from being recognized by programs # run on this text file BEGIN { Name = "libupdate" Usage = \ "Usage: " Name \ " [-hdSqBrpPNRxn] [-s] [-b] [-e]\n"\ " [-E] [-V] [-[cCM]] newlib [file ...]" rcFile = ".libupdate" # Used: bBcCdefFEhmMNpPqrRsSuUvVWx ARGC = Opts(Name,Usage,"BE;s:SdrqRhxb;e;V;c:C;pPNM;u;U;",0, "~/" rcFile ":$UHOME/" rcFile,"NOBACKUP,EXTENSION,STARTSTRING,"\ "NOSTARTSTRING,NOCHECK,REQVERSION,QUIET,REGEX", 0,"n",0,"", "s,S;d,Vr;c,C;p,VrCd;P,EVrp;N,cCEVr;M,cCpsSbeRVr;uU,cCMBEsSbeRdVrpPN;u,U") startString = "### " if ("h" in Options) { printf \ "%s: Update libraries included in the text of interpreted programs.\n"\ "%s\n"\ " newlib is the filename of a library to substitute into each of the\n"\ "other named files as a replacement of an older version of the library.\n"\ "The substitution is done by getting the first and last lines of newlib,\n"\ "finding the identical copies of those lines in the file being processed,\n"\ "and replacing the lines and everything between them with contents of the\n"\ "new library. The first and last lines must be different and there must be\n"\ "only one instance of each in each processed file. Both the first and last\n"\ "lines of the library are normally required to begin with the string\n"\ "\"%s\" as a sanity check; this can be changed with the -s option.\n"\ " If newlib contains an SCCS string of the form\n"\ "\"@(\#) \", then each replaced\n"\ "library will be searched for an SCCS string that begins with\n"\ "\"@(\#) \". If such a string is found and the version number or\n"\ "date is later than that in the replacement library, a warning is issued\n"\ "and the file is not updated. This can be turned off with the -d option.\n"\ " should have the form year/month/day. should contain a\n"\ "sequence of numeric strings separated by '.' characters. Each pair of\n"\ "numeric strings is compared (as integers) separately, with strings on the\n"\ "left having higher value.\n"\ " A copy of each file is put in filename- when the file is processed.\n"\ "If no filenames are given, a single file is read from the standard input\n"\ "and the modified version is written to the standard output.\n"\ "Options:\n"\ "Some of the following options can also be set by assigning values to\n"\ "variables in a configuration file named %s. This file is searched for\n"\ "first in the home directory of the invoking user and then, if the\n"\ "environment variable UHOME is set, in the directory specified by that\n"\ "variable. If both files exist, variables assignments in the first file\n"\ "override those in the second file. Variables are assigned to with the\n"\ "syntax: varname=value or in the case of flags, by simply putting the\n"\ "indicated variable name in the file without a value. To turn off a flag\n"\ "option in the first file and prevent it from being set in the second file,\n"\ "assign it a value of 0. e.g. if FOO is set in the first file, FOO=0 in\n"\ "the second file will override it. Flag options can be turned off on the\n"\ "command line by following them immediately with \"-\", e.g. -v- to turn\n"\ "off the v option in such a way that it cannot be turned on in a config\n"\ "file. Variable names appear in parentheses in the option descriptions.\n"\ "-h: Print this help.\n"\ "-c: Compare the library found in each file line-for-line to\n"\ " the the contents of the library files given in the colon- or\n"\ " whitespace-separated list . If the library in a file is\n"\ " not exactly the same as that in one of the compare-libs, a complaint\n"\ " is issued and no update is done.\n"\ "-C: Like -c, except that the only result of a lack of match\n"\ " is the complaint; the update is still done.\n"\ "-M: Like -c, except that no checks for matching first or\n"\ " last line are done. Instead, each file to be processed is searched\n"\ " until a sequence of lines matching that in one of the given files is\n"\ " found. That set of lines is then replaced with the new library. The\n"\ " search done by this option may take a long time.\n"\ "-B: Do not makes backup copies of processed files. (NOBACKUP)\n"\ "-E: Instead of making backup copies, write updated files to the same\n"\ " filename with appended. (EXTENSION)\n"\ "-x: Turn on debugging.\n"\ "-s: Require the first and last lines of newlib to start with\n"\ " . (STARTSTRING)\n"\ "-S: Do not require the first and last lines of newlib to start with any\n"\ " special string; equivalent to -s \"\". (NOSTARTSTRING)\n"\ "-b: Use as the starting line of the library to\n"\ " search for in each file.\n"\ "-e: Use as the ending line of the library to search\n"\ " for in each file.\n"\ "If multiple -b or -e options are given, each will be searched for.\n"\ "-R: Treat the strings given with -b and -e as unanchored regular\n"\ " expressions style of egrep rather than as fixed strings. (REGEX)\n"\ "-d: Do not check library version numbers or dates. (NOCHECK)\n"\ "-V: Use as the source of the new library\n"\ " version number and date instead of reading it from the new library.\n"\ " It should have three words: \n"\ "-r: Require replaced libraries to have a version string for comparison.\n"\ " This also requires the new library to have a version string, unless\n"\ " -V is given. (REQVERSION)\n"\ "-n: Do not read any configuration files.\n"\ "-q: Quiet. Turns off some messages. (QUIET)\n"\ "The following options cause %s to generate output based on the\n"\ "contents of each file rather than updating them. If any is given with\n"\ "both -b and -e, then no should be given; i.e. the first filename\n"\ "argument to %s should be the first file to be processed rather than a\n"\ "library file. The same is true if -M is given with -P or -N.\n"\ "-p: Print the library contained in a file. If -E is also given, the\n"\ " library is written to a filename as described for the -E option. If\n"\ " not, the library is written to the standard output; in this case, only\n"\ " one source filename should be given. The -d option is always turned\n"\ " on. If -c is given with -p, the meaning of the -c option is inverted:\n"\ " output will be produced only if the library contained in a file is NOT\n"\ " identical to any of the named libraries. Additionally, each library\n"\ " read from a file will be added to the set of libraries for comparison,\n"\ " so only one instance of each unique library will be output. An empty\n"\ " string may be given to -c to turn on this behaviour without having to\n"\ " name any already-existing libraries for comparison.\n"\ "-P: Print the version string found in each file.\n"\ "-N: Print the names of files that no library is found in.\n"\ "-u: Search the entirity of each file for an SCCS string with the\n"\ " format described above and the name . For the first such string\n"\ " found in each file, a line with this format is printed:\n"\ " filename version date line-number\n"\ "-U: Like -u, except that only the first instance of each version\n"\ " found is printed.\n", Name,Usage,startString,rcFile,Name,Name exit 0 } if ("U" in Options) { vName = Options["U"] uniqueVersion = 1 } if ("u" in Options) vName = Options["u"] if (Debug = "x" in Options) printf "Debugging is on.\n" numFiles = ARGC - 1 argNum = 1 if ((regEx = "R" in Options) && Options["R","num",1] != 0 && \ !("b" in Options || "e" in Options)) { # If -R given on command line w/o -b or -e... printf "%s: Must give -b or -e if -R is given. Exiting.\n", Name > "/dev/stderr" exit 1 } matchLibs = "M" in Options printLib = "p" in Options printLibVersion = "P" in Options noLib = "N" in Options printOnly = printLib || printLibVersion || noLib if (!(printOnly && ("b" in Options && "e" in Options || "M" in Options) \ || vName != "")) { if (!numFiles) { printf "%s: No library named. Use -h for help.\n", Name > "/dev/stderr" exit 1 } libFile = ARGV[1] libLines = readLib(libFile,library) numFiles-- argNum++ } backup = !("B" in Options) versionRequired = "r" in Options quiet = "q" in Options || (!Debug && printOnly) if (compareWarning = ("C" in Options)) { libFiles = Options["C"] compareLibs = 1 } else if ("c" in Options) { compareLibs = 1 libFiles = Options["c"] if (printLib) addLib = 1 } else if (matchLibs) { compareLibs = 1 libFiles = Options["M"] } checkVersion = !(("d" in Options) || printOnly || compareLibs) if ("E" in Options) { if (!numFiles) { printf \ "%s: Cannot use -E option when reading from standard input. Exiting.\n", Name > "/dev/stderr" exit 1 } newExt = Options["E"] backup = 0 } else if (printLib && numFiles > 1) { printf "%s: may only give one filename if -p is used without -E.\n", Name > "/dev/stderr" exit 1 } if ("s" in Options) startString = Options["s"] else if ("S" in Options || "M" in Options) startString = "" if (!matchLibs && vName == "") { getLibMarker("first",startString,libFile,Options,"b",library[1], firstLines) getLibMarker("last",startString,libFile,Options,"e",library[libLines], lastLines) if (Intersection(firstLines,lastLines,Common)) { printf "%s: Got identical first and last lines (exiting):\n", Name > "/dev/stderr" for (line in Common) print line > "/dev/stderr" exit 1 } } if (libLines) { if ("V" in Options) { if (!getVersion(Options["V"],versionInfo,1)) { printf "%s: Bad version string: %s\n",Name, Options["V"] > "/dev/stderr" exit 1 } } else if (\ !getLibVersion(library,1,0,versionInfo,quiet,libFile,versionRequired)) checkVersion = 0 } if (!numFiles) { numFiles++ ARGV[argNum] = "/dev/stdin" } if (!matchLibs && Debug) { printf "%d library starting %s(s):\n%s\n"\ " %d library ending %s(s):\n%s\n",NumElem(firstLines), regEx ? "pattern" : "line",conglom(firstLines,"\n"),NumElem(lastLines), regEx ? "pattern" : "line",conglom(lastLines,"\n") > "/dev/stderr" printf "%d file(s) to be processed.\n",numFiles > "/dev/stderr" } if (compareLibs) if (!readLibs(libFiles,firstLines,lastLines,libs,versionRequired,quiet, (name in versionInfo) ? versionInfo["name"] : "",regEx,"b" in Options, "e" in Options,matchLibs) && !(compareWarning || printLib)) { printf "%s: No libraries given.\n",Name > "/dev/stderr" exit 1 } for (; numFiles; numFiles--) procFile(ARGV[argNum++],library,firstLines,lastLines,backup, Debug,checkVersion,quiet,versionInfo,newExt,versionRequired,printLib, printLibVersion,noLib,compareLibs,compareWarning,libs,regEx,matchLibs, addLib,vName,uniqueVersion) exit 0 } function conglom(set,sep, s,l) { for (l in set) s = s sep l return substr(s,1+length(sep)) } # Reads each file in libFiles into libs. # The lines of each file are placed in libs[filenum,1..numlines] # where filenum is an integer starting with 1. # The number of files is stored in libs["numfiles"]. # The number of lines read from each file is stored in libs[filenum,"numlines"] # The number of chars read from each file is stored in libs[filenum,"numchars"] # The name of each file is stored in libs[filenum,"name"]. # The version string of each file (if any) is stored in libs[filenum,"version"] # If matchLibs is true, the first & last lines of each lib are added to # firstLines[] and lastLines[] function readLibs(libFiles,firstLines,lastLines,libs,versionRequired,quiet, libName,regEx,b,e,matchLibs, nFiles,i,file,libNum,numLines,versionInfo) { nFiles = split(libFiles,files,"[ \t:]+") for (i = 1; i <= nFiles; i++) { file = files[i] if (file == "") continue ++libNum libs[libNum,"name"] = file numLines = readLib(file,libs,libNum SUBSEP) if (matchLibs) { firstLines[f = libs[libNum,1]] lastLines[l = libs[libNum,numLines]] if (Debug) printf \ "Adding to firstLines[]: %s\nAdding to lastLines[]: %s\n", f,l > "/dev/stderr" } else if (\ (!matchLines(libs[libNum,1],firstLines,regEx,"first",b,file,1) || !matchLines(libs[libNum,numLines],lastLines,regEx,"last",e,file,1))) exit 1 if (getLibVersion(libs,1,0,versionInfo,quiet,file,versionRequired, libNum SUBSEP)) { libs[libNum,"version"] = versionString(versionInfo) if (versionRequired && versionInfo["name"] != libName) { printf \ "%s: name in version string of library in file %s\n"\ " (%s) does not match name in replacement library (%s). Exiting.\n",Name, file,versionInfo["name"],libName > "/dev/stderr" exit 1 } } } libs["numfiles"] = libNum return libNum } function matchLines(line,compareLines,regEx,lineName,lineGiven,file,complain, compareLine) { if (regEx) { for (compareLine in compareLines) if (line ~ compareLine) return 1 } else if (line in compareLines) { if (Debug) printf "Found: %s\n",line > "/dev/stderr" return 1 } if (complain) printf \ "%s: %s line of comparison library file %s\n"\ " does not match %s line %s. Exiting.\n",Name, lineName,file,lineName,regEx ? "pattern(s)" : (lineGiven ? \ "comparison string(s)" : "of replacement library(s)") > "/dev/stderr" return 0 } function readLib(libFile,library,prefix, libLines) { if (Debug) printf "Reading library file: \"%s\"\n",libFile > "/dev/stderr" if ((libLines = readFile(libFile,library,prefix)) == -1) { printf "%s: Error reading library file \"%s\": %s\n",Name,libFile, ERRNO > "/dev/stderr" exit 1 } if (!libLines) { printf "%s: Library file \"%s\" is empty. Exiting.\n",Name, libFile > "/dev/stderr" exit 1 } return libLines } function getLibMarker(lineName,startString,libFile,Options,Opt,libLine,lines, i) { if (Opt in Options) { lines[Options[Opt]] if (Debug) printf "Got %s-line comparison line: %s\n",lineName, Options[Opt] > "/dev/stderr" for (i = Options[Opt,"count"]; i > 1; i--) { lines[Options[Opt,i]] if (Debug) printf "Got %s-line comparison line: %s\n",lineName, Options[Opt,i] > "/dev/stderr" } } else if (substr(libLine,1,length(startString)) != startString) { printf \ "%s: %s line of library file \"%s\"\n"\ " does not begin with string \"%s\". Use -s or -S to override. Exiting.\n", Name,lineName,libFile, startString > "/dev/stderr" exit 1 } else { if (Debug) printf "%s line of new library begins with \"%s\"\n",lineName, startString > "/dev/stderr" lines[libLine] } if (Debug) printf "Got %d %s-line comparison line(s).\n",NumElem(lines), lineName > "/dev/stderr" } # library[] is a library to search for a version string. # It contains lines with integer indices. # first is the first line number. # If last is non-0, it is the last line number. # If quiet is false, the library version is printed. # libFile is the file that library came from, for messages. # If versionRequired is true, not finding a version string is fatal. # If Prefix is non-null, it is used as a prefix for the indices used to # access library[]. # If libname is non-null, any version string that has a name that doesn't # match it is ignored. # If a line is found, the version components are put in versionInfo[]. # Return value: # Line number version string was found on if version line is found, 0 if not. function getLibVersion(library,first,last,versionInfo,quiet,libFile, versionRequired,Prefix,libname, i,ret) { ret = 0 if (Debug) printf \ "getLibVersion: searching for version on lines %d through %s; prefix is: %s\n", first,last ? last : "end",Prefix > "/dev/stderr" for (i = first; last ? (i <= last) : ((Prefix i) in library); i++) { if (Debug) printf "%d\r",i > "/dev/stderr" if (library[Prefix i] ~ /@\(#\)/) { if (Debug) printf "Found SCCS line in library file %s:\n%s\n",libFile, library[Prefix i] > "/dev/stderr" if (getVersion(library[Prefix i],versionInfo)) { if (libname != "" && versionInfo["name"] != libname) { if (Debug) printf \ "In %s, found version string with name \"%s\";\n"\ " doesn't match name being searched for (%s); ignoring...\n",libFile, versionInfo["name"],libname > "/dev/stderr" # Empty versionInfo[] since the existance of indexes in it # may be used as a test of whether a good version line was read split("",versionInfo) continue } ret = i break } } } if (ret) { if (!quiet) printf "Library in file %s is: %s\n",libFile, versionString(versionInfo) > "/dev/stderr" } else { if (Debug) printf "No version line in library in file %s\n", libFile > "/dev/stderr" if (versionRequired) { printf \ "%s: No version string found in library in file \"%s\". Exiting.\n", Name,libFile > "/dev/stderr" exit 1 } } return ret } # If line ends with a string of the form: # @( #) # ^ avoid confusing programs run on this text file # then the string is put in versionInfo["name"], version-# is put in # versionInfo["ver"], and date is put in versionInfo["date"]. # must have the form num/num/num. must consist of a # sequence of numeric strings separated by '.' characters. # If noSCCS is true, the string should contain only the name, version, and # date words. # If line does not contain a string of this form, 0 is returned and no # indexes are created in versionInfo; if it does, 1 is returned. function getVersion(line,versionInfo,noSCCS, pos,elem,name,version,date,nElem) { if (!noSCCS) { if (!(pos = index(line,"@(\#)"))) return 0 line = substr(line,pos+4) } sub("^[ \t]+","",line) # get rid of leading & trailing whitespace sub("[ \t]+$","",line) if ((nElem = split(line,elem,"[ \t]+")) != 3) { if (Debug) printf \ "Found %d words after @(\#) on following line; wanted 3.\n%s\n", nElem,line > "/dev/stderr" return 0 } name = elem[1] version = elem[2] date = elem[3] if (version !~ /^[0-9]+(\.[0-9]+)*$/) { if (Debug) printf \ "Version word '%s' not of recognized format in following line:\n%s\n", version,line > "/dev/stderr" return 0 } if (date !~ "^[0-9]+/[0-9]+/[0-9]+$") { if (Debug) printf \ "Date word '%s' not of recognized format in following line:\n%s\n", date,line > "/dev/stderr" return 0 } versionInfo["name"] = name versionInfo["ver"] = version versionInfo["date"] = date return 1 } # v1 and v2 are sequences of integers separated by the pattern Sep. # Each pair of integers (from v1 and v2) is compared in turn, starting from # the left. If an int from v1 is < int from v2, -1 is returned. # If v1 and v2 compare the same, 1 is returned. If an int from v1 is > int # from v2, 1 is returned. If one of v1 or v2 runs out of integers before the # other, the one that runs out is taken to be < the one that still has # integers, even if the one that still has integers has a 0 as the next value. function compareVersion(v1,v2,Sep, v1e,v2e,i,i1,i2) { split(v1,v1e,Sep) split(v2,v2e,Sep) for (i = 1; i in v1e; i++) if (!(i in v2e)) # ran out of ints for v2, so v1 > v2 return 1 else { i1 = v1e[i]+0 i2 = v2e[i]+0 if (i1 > i2) return 1 else if (i2 > i1) return -1 } if (i in v2e) # ran out of ints for v1, so v2 > v1 return -1 return 0 } function versionString(version) { return version["name"] " " version["ver"] " " version["date"] } # Read file into array lines, one line per element, with integer indices # starting at 1. # The index "numchars" is set to the total number of characters read. # The index "numlines" is set to the total number of lines read. # If prefix is set, all array indices start with it. # Return value: # -1 on error; the number of lines read on success. function readFile(file,lines,prefix, numLines,ret,numChars) { while ((ret = (getline < file)) == 1) { numLines++ lines[prefix numLines] = $0 numChars += length($0)+1 } if (Debug) printf "Read %d lines from file %s. Last getline returned %d.\n", numLines,file,ret > "/dev/stderr" close(file) if (!ret) { lines[prefix "numchars"] = numChars lines[prefix "numlines"] = numLines } return ret ? -1 : numLines } function writeLines(outFile,Lines,first,last, i) { for (i = first; i <= last || (!last && (i in Lines)); i++) print Lines[i] > outFile return 1 } # Compare the library in lines[] to each libary in libs[]. # The lines of each library are in libs[libnum,1..numlines] # where libnum is an integer starting with 1. # The number of libraries is in libs["numfiles"]. # The number of lines in each library is in libs[libnum,"numlines"] # The number of chars in each library is in libs[libnum,"numchars"] # The name of the file each library came from is in libs[libnum,"name"]. # The version string of each library (if any) is in libs[libnum,"version"] # Return value: Library number if a match is found, else 0. # If a match is not found and addLib is true, the library being searched for # is added to libs[] (the version string is not added). function doCompareLibs(lines,libs,fileName,addLib,firstLineNum,Debug, lastLineNum,numChars,numLibs,numCompareLibLines,lineNum,libNum,fLineNum, maxIncLibLines,numLines) { numLibs = libs["numfiles"] if (firstLineNum) { maxIncLibLines = lines["numlines"] - firstLineNum + 1 if (Debug) printf "Maximum included lib length: %d lines\n", maxIncLibLines > "/dev/stderr" } else { firstLineNum = lines["libstart"] lastLineNum = lines["libend"] numChars = lines["libnumchars"] numLines = lastLineNum - firstLineNum + 1 if (Debug) printf \ "Included library has %d lines, %d chars. Comparing to %d libs.\n", numLines, numChars, numLibs > "/dev/stderr" } for (libNum = 1; libNum <= numLibs; libNum++) { numCompareLibLines = libs[libNum,"numlines"] if (numLines) { if (numLines != numCompareLibLines) { if (Debug) printf "Included library is not \"%s\"; it has %d lines.\n", libs[libNum,"name"],numCompareLibLines > "/dev/stderr" continue } if (libs[libNum,"numchars"] != numChars) { if (Debug) printf "Included library is not same as \"%s\";\n"\ " # of lines match but it has %d characters.\n", libs[libNum,"name"],libs[libNum,"numchars"] > "/dev/stderr" continue } } else if (numCompareLibLines > maxIncLibLines) { if (Debug) printf \ "Included library is not \"%s\"; it has too many lines (%d).\n", libs[libNum,"name"],numCompareLibLines > "/dev/stderr" continue } fLineNum = firstLineNum for (lineNum = 1; lineNum <= numCompareLibLines; lineNum++) { if (libs[libNum,lineNum] != lines[fLineNum]) { if (Debug) printf \ "Included library is not \"%s\"; mismatch at line %d:\n"\ " Included lib: %s\n"\ " Compared lib: %s\n", libs[libNum,"name"],lineNum,lines[fLineNum], libs[libNum,lineNum] > "/dev/stderr" break } fLineNum++ } if (lineNum > numCompareLibLines) { if (!quiet) printf \ "%s: Included library in file \"%s\" is identical to \"%s\"\n", Name,fileName,libs[libNum,"name"] > "/dev/stderr" return libNum } } if (addLib) { libs["numfiles"] = ++numLibs if (Debug) printf "Adding library from \"%s\" to library set as lib #%d...\n", fileName,numLibs > "/dev/stderr" libs[numLibs,"numchars"] = numChars libs[numLibs,"numlines"] = numLines libs[numLibs,"name"] = fileName fLineNum = firstLineNum for (lineNum = 1; lineNum <= numLines; lineNum++) libs[numLibs,lineNum] = lines[fLineNum++] } return 0 } function checkByMatch(lines,inFileName,firstLines,libs,Name,Debug, libNum, numLines,lineNum,line) { numLines = lines["numlines"] for (lineNum = 1; lineNum <= numLines; lineNum++) { if (Debug) printf "%d\r",lineNum > "/dev/stderr" line = lines[lineNum] if (line in firstLines && (libNum = doCompareLibs(lines,libs,inFileName,0,lineNum,Debug))) break } if (libNum) { lines["libstart"] = lineNum lines["libend"] = lineNum + libs[libNum,"numlines"] - 1 return 1 } else { printf "%s: None of %d comparison libraries found in file %s\n", Name,libs["numfiles"],inFileName > "/dev/stderr" return 0 } } # Find a section of a file by searching for a match from among a set of first # and last lines or patterns. # Input variables: # lines[]: the array of lines to search. The lines have integer indexes # starting at 1. # firstLines[], lastLines[]: Sets of lines that may mark the start and end # of the library being searched for. # regEx: whether to treat the above sets as fixed lines or patterns. # inFileName: The name of the file, for use in messages. # Name: name of program, for error messages. # Output variables: # On success, lines["libstart"] and lines["libend"] are set to the first and # last lines of the found library, and lines["libnumchars"] is set to the # total number of characters in the library. # Return value: # On error (last line found before first, multiple instances of lines # found, or first but not last line found), -1. # On success, 1. # On failure (first and last lines not found), 0. function findFirstLast(lines,firstLines,lastLines,regEx,inFileName,Name, firstLineNum,lastLineNum,lineNum,numLines,numChars,line) { if (Debug) printf \ "findFirstLast(): searching for any of %d start line(s), %d end line(s).\n", NumElem(firstLines),NumElem(lastLines) > "/dev/stderr" numLines = lines["numlines"] for (lineNum = 1; lineNum <= numLines; lineNum++) { if (Debug) printf "%d\r",lineNum > "/dev/stderr" line = lines[lineNum] # If found multiple instances of 1st line, fail. if (matchLines(line,firstLines,regEx)) { # if found match for 1st line if (!(firstLineNum = checkFoundLine(firstLineNum,lineNum,"first", inFileName,lines,line))) return -1 } if (firstLineNum && !lastLineNum) numChars += length(line)+1 if (matchLines(line,lastLines,regEx)) { if (!firstLineNum) { printf \ "%s: Found match for last line of library in file\n"\ " \"%s\" (at line %d)\n"\ " before match for first line of library. Skipping this file.\n", Name,inFileName,lineNum > "/dev/stderr" return -1 } # If found multiple instances of last line, fail. if (!(lastLineNum = checkFoundLine(lastLineNum,lineNum,"last", inFileName,lines,line))) return -1 } } # Test old library if (!firstLineNum) return 0 if (!lastLineNum) { printf "%s: Did not find end of library in file (skipping): %s\n", Name,inFileName > "/dev/stderr" return -1 } lines["libstart"] = firstLineNum lines["libend"] = lastLineNum lines["libnumchars"] = numChars return 1 } # Search a set of lines for a library. The library section is recognized by # its first and last lines and possibly its version string. # Input variables: # lines[]: the array of lines to search. The lines have integer indexes # starting at 1. # inFileName: The name of the file, for use in messages. # firstLines[], lastLines[]: Sets of lines that may mark the start and end # of the library being searched for. # regEx: whether to treat the above sets as fixed lines or patterns. # compareLibs: true if the lines of a found library must match one of a given # set of libraries. # libs[]: The lines of the libraries for the above comparison. # addLib: True if a non-matching library should be added to the library set. # checkVersion: If true and library section contains a version string, it # must be equale to or later than the version in libVersion[]. # versionRequired: If true, library section must contain a version string. # getVersion: True if version information should be returned in # versionInfo[], but not necessarily checked. # libVersion[]: Version information from replacement library. # Name: Program name, for messages. # Debug: Print debugging info. # Output variables: # If version information is gotten, it is put in versionInfo[]. # If a good library is found in lines[], the first and last line numbers of # the library are put in lines["libstart"] and lines["libend"], and the # number of characters in the library is put in lines["libnumchars"] # Return value: # 1 on success. # 0 if library not found. # -1 if a bad library was found. # -2 if library contents comparison fails. function checkByFirstLast(lines,inFileName,firstLines,lastLines,regEx, compareLibs,libs,addLib,Name,Debug, ret,numCompared) { ret = findFirstLast(lines,firstLines,lastLines,regEx,inFileName,Name) if (ret != 1) return ret if (compareLibs) { # Get number of current libs first, since doCompareLibs() may change it numCompared = libs["numfiles"] if (!doCompareLibs(lines,libs,inFileName,addLib,0,Debug)) { printf "%s: No match for library in file %s\n"\ " found in any of %d comparison library(s).\n",Name, inFileName,numCompared > "/dev/stderr" return -2 } } return 1 } # Globals: versions[] function procFile(inFile,library,firstLines,lastLines,backup,Debug, checkVersion,quiet,libVersion,newExt,versionRequired,printLib,printLibVersion, noLib,compareLibs,compareWarning,libs,regEx,matchLibs,addLib,vName, uniqueVersion, inFileName,contentsRet,versionInfo,lines,outFile,backupFileName,vString) { inFileName = (inFile == "/dev/stdin" ? "(standard input)" : inFile) if (Debug) printf "\nProcessing file: \"%s\"\n",inFile > "/dev/stderr" if (readFile(inFile,lines) == -1) { printf "%s: Error reading file %s. Skipping.\n",Name, inFileName > "/dev/stderr" return 0 } if (vName != "") { if (ret = getLibVersion(lines,1,0,versionInfo,1,inFileName,0,"",vName)) { vString = versionInfo["ver"] " " versionInfo["date"] if (uniqueVersion) { if (vString in versions) { if (Debug) printf "Already saw version <%s>\n", vString > "/dev/stderr" return 1 } else { if (Debug) printf "New version <%s>\n",vString > "/dev/stderr" versions[vString] } } printf "%s %s %d\n",inFileName,vString,ret return 1 } else { if (!uniqueVersion) printf "%s no-version\n",inFileName return 0 } } if (matchLibs) contentsRet = \ checkByMatch(lines,inFileName,firstLines,libs,Name,Debug) else { contentsRet = checkByFirstLast(lines,inFileName,firstLines,lastLines, regEx,compareLibs,libs,addLib,Name,Debug) if (Debug) printf "checkByFirstLast() returned %d\n", contentsRet > "/dev/stderr" } if (!contentsRet) { # no lib found if (noLib) print inFileName else printf \ "%s: Did not find start or end of library in file (skipping): %s\n", Name,inFileName > "/dev/stderr" return 0 } if (contentsRet == -1) # bad lib return 0 if (printLib || printLibVersion || checkVersion || versionRequired) { if (getLibVersion(lines,lines["libstart"],lines["libend"],versionInfo, 1,inFileName,0,"",libVersion["name"])) { if (checkVersion && \ (compareLibVersions("version","\\.",versionInfo["ver"], libVersion["ver"],inFileName) == 1 || compareLibVersions("date","/",versionInfo["date"], libVersion["date"],inFileName) == 1)) return 0 } else if (versionRequired) { printf \ "%s: Did not find version string in library in file (skipping): %s\n", Name,inFileName > "/dev/stderr" return 0 } } if (printLibVersion) { if ("name" in versionInfo) printf "%-12s %s\n",inFileName,versionString(versionInfo) else printf "%s: no version string.\n",inFileName > "/dev/stderr" return ("name" in versionInfo) } if (printLib) { if ("name" in versionInfo) printf "Library version in %s is %s\n",inFileName, versionString(versionInfo) > "/dev/stderr" # If either we are not doing a contents comparision or we are and it # failed, write lib contents if (!compareLibs || contentsRet == -2) { if (newExt != "") outFile = inFile newExt else outFile = "/dev/stdout" printf "Writing library from %s (%d lines) to %s\n",inFileName, lines["libend"] - lines["libstart"] + 1,outFile > "/dev/stderr" if (!writeLines(outFile,lines,lines["libstart"],lines["libend"])) return -1 close(outFile) return 1 } else return 0 } if (noLib) return 1 # If contents comparisons didn't succeed and this should be fatal, return if (contentsRet != 1 && !compareWarning) return 0 # Back up old file if (backup && (inFile != "/dev/stdin")) { backupFileName = inFile "-" if (Debug) printf "Backing up \"%s\" to \"%s\"\n",inFile, backupFileName > "/dev/stderr" if (copy(inFile,backupFileName)) { printf "%s: Could not make backup copy of file \"%s\" in \"%s\".\n"\ " Skipping this file.\n",Name,inFile,backupFileName > "/dev/stderr" return 0 } } # Write updated file if (newExt != "") outFile = inFile newExt else if (inFile == "/dev/stdin") outFile = "/dev/stdout" else outFile = inFile if (Debug) printf \ "Writing output (replacing lines %d through %d) to file: \"%s\"\n", lines["libstart"],lines["libend"],outFile > "/dev/stderr" if (!writeLines(outFile,lines,1,lines["libstart"]-1) || !writeLines(outFile,library,1,0) || !writeLines(outFile,lines,lines["libend"]+1,0)) { printf "File \"%s\" may be corrupted!\n",outFile > "/dev/stderr" if (backup) printf "Original file contents are in file \"%s\"\n", backupFileName > "/dev/stderr" return -1 } close(outFile) # Print status if (!quiet) { printf "%s: Updated file \"%s\"",Name,inFileName > "/dev/stderr" if ("name" in versionInfo) printf " (had library %s)", versionString(versionInfo) > "/dev/stderr" print "" > "/dev/stderr" } return 1 } # Compare version element in oldVersion to that in libVersion. # See compareVersion() for a description of the format of the version strings # and Sep. # verName is the version component being compared. # inFile is the name of the file that oldVersion came from. # Return value: # -1 if oldVersion is earlier/lower than libVersion # 0 if oldVersion is equale to libVersion # 1 if oldVersion is later/higher than libVersion function compareLibVersions(verName,Sep,oldVersion,libVersion,inFile, rel) { rel = compareVersion(oldVersion,libVersion,Sep) if (rel == 1) { printf "%s: Library %s in file \"%s\" (%s)\n"\ " appears to be later than %s of new library %s (%s).\n"\ " Skipping this file.\n", Name,verName,inFile,oldVersion,verName,verName, libVersion > "/dev/stderr" } else if (!rel) printf "%s: Note: Library %s in file \"%s\" (%s)\n"\ " appears to be the same as %s of new library %s (%s). Continuing.\n", Name,verName,inFile,oldVersion,verName,verName, libVersion > "/dev/stderr" if (rel == 1 && Debug) printf "File \"%s\": Old library %s was %s.\n",inFile,libVersion, verName > "/dev/stderr" return rel } function checkFoundLine(oldLineNum,lineNum,lineName,inFileName,lines, line2) { if (oldLineNum) { printf \ "%s: Found multiple instances of %s line of library in file\n"\ " \"%s\". Skipping this file.\n"\ " 1st (line %d): %s\n"\ " 2nd (line %d): %s\n", Name,lineName,inFileName,oldLineNum, lines[oldLineNum],lineNum,line2 > "/dev/stderr" return 0 } else { if (Debug) printf "File \"%s\": Found %s line of library at line %d.\n", inFileName,lineName,lineNum > "/dev/stderr" return lineNum } } ### Begin copy,append routines # append: append file Source to file Dest. # The final return value from the read is returned. # It will be 0 if the file was read successfully; -1 if not. function append(Source,Dest, Line,ret) { while ((ret = (getline Line < Source)) == 1) print Line >> Dest close(Source) close(Dest) return ret } # copy: append file Source to file Dest. # The final return value from the read is returned. # It will be 0 if the file was read successfully; -1 if not. function copy(Source,Dest, Line,ret) { while ((ret = (getline Line < Source)) == 1) print Line > Dest close(Source) close(Dest) return ret } ### End copy,append routines ### Start of ProcArgs library # @(#) ProcArgs 1.11 96/12/08 # 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(): Do not require any args if h option is given. # 95/01/22 Record options given more than once. Record option num in argv. # 95/06/08 Added ExclusiveOptions(). # 96/01/20 Let rcfiles be a colon-separated list of filenames. # Expand $VARNAME at the start of its filenames. # Let varname=0 and -option- turn off an option. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many # of the vars should be searched for in the environment. # Check for duplicate rcfiles. # 96/05/13 Return more specific error values. Note: ProcArgs() and InitOpts() # now return various negatives values on error, not just -1, and # Opts() may set Err to various positive values, not just 1. # Added AllowUnrecOpt. # 96/05/23 Check type given for & option # 96/06/15 Re-port to awk # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be # used by other functions. # 96/10/15 Added OptChars # 96/11/01 Added exOpts arg to Opts() # 96/11/16 Added ; type # 96/12/08 Added Opt2Set() & Opt2Sets() # 96/12/27 Added CmdLineOpt() # 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 # ; Non-empty 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 ":" # or ";". # 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 flaw that caused problems in some cases. See the OptChars # parameter to explicitly set the option-specifier characters. # 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 the # number of times it occurs in argv[]. # 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, and Options[option-name,"count"] is (initially) set to the 1. # If an option that takes an argument is given more than once, # Options[option-name,"count"] is incremented, and the value is assigned to # the index (option-name,instance) where instance is 2 for the second occurance # of the option, etc. # In other words, the first time an option with a value is encountered, the # value is assigned to an index consisting only of its name; for any further # occurances of the option, the value index has an extra (count) dimension. # The sequence number for each option found in argv[] is stored in # Options[option-name,"num",instance], where instance is 1 for the first # occurance of the option, etc. The sequence number starts at 1 and is # incremented for each option, both those that have a value and those that # do not. Options set from a config file have a value of 0 assigned to this. # 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. # Option processing will stop with the first unrecognized option, just as # though -- was given except that unlike -- the unrecognized option will not be # removed from ARGV[]. Normally, an error value is returned in this case. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to # be found, so the number of remaining arguments is returned instead. # If OptChars is not a null string, it is the set of characters that indicate # that an argument is an option string if the string begins with one of the # characters. A string consisting solely of two of the same option-indicator # characters stops the scanning of argv[]. The default is "-+". # 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 a negative value is returned. # Current error values: # -1: option that required an argument did not get it. # -2: argument of incorrect type supplied for an option. # -3: unrecognized (invalid) option. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven, NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet) { # 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,"&") OptionNum = 0 if (OptChars == "") OptChars = "-+" while (OptChars != "") { c = substr(OptChars,1,1) OptChars = substr(OptChars,2) OptCharSet[c] OptTerm[c c] } for (ArgNum = 1; ArgNum < argc; ArgNum++) { Arg = argv[ArgNum] if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet)) break # Not an option; quit if (Arg in OptTerm) { delete argv[ArgNum] ArgsLeft-- break } ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) if (NumOpt && Option ~ /[-+.0-9]/) { # If this option is a numeric option, make its flag be & and # its option string flag position be the position of & in # the option string. Option = "&" Pos = NumOpt # Prefix Arg with a char so that ArgInd will point to the # first char of the numeric option. Arg = "&" Arg ArgLen++ } # Find position of flag in option string, to get its type (if any). # Disallow & as literal flag. else if (!(Pos = index(OptList,Option)) || Option == "&") { if (AllowUnrecOpt) { Escape = 1 break } else { OptErr = "Invalid option: " specGiven Option return -3 } } # Find what the value of the option will be if it takes one. # NeedNextOpt is true if the option specifier is the last char of # this arg, which means that if the option requires a value it is # the next arg. if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg if (GotValue = ArgNum + 1 < argc) Value = argv[ArgNum+1] } else { # Value is included with option Value = substr(Arg,ArgInd + 1) GotValue = 1 } if (HadValue = AssignVal(Option,Value,Options, substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt, specGiven)) { if (HadValue < 0) # error occured return HadValue if (HadValue == 2) ArgInd++ # Account for the single-char value we used. else { if (NeedNextOpt) { # option took next arg as value delete argv[++ArgNum] ArgsLeft-- } break # This option has been used up } } } if (Escape) break # Do not delete arg until after processing of it, so that if it is not # recognized it can be left in ARGV[]. delete argv[ArgNum] ArgsLeft-- } if (compress != 0) { dest = 1 src = argc - ArgsLeft + 1 for (count = ArgsLeft - 1; count; count--) { ARGV[dest] = ARGV[src] dest++ src++ } } return ArgsLeft } # Assignment to values in Options[] occurs only in this function. # Option: Option specifier character. # Value: Value to be assigned to option, if it takes a value. # Options[]: Options array to return values in. # ArgType: Argument type specifier character. # GotValue: Whether any value is available to be assigned to this option. # Name: Name of option being processed. # OptionNum: Number of this option (starting with 1) if set in argv[], # or 0 if it was given in a config file or in the environment. # SingleOpt: true if the value (if any) that is available for this option was # given as part of the same command line arg as the option. Used only for # options from the command line. # specGiven is the option specifier character use, if any (e.g. - or +), # for use in error messages. # Global variables: OptErr # Return value: negative value on error, 0 if option did not require an # argument, 1 if it did & used the whole arg, 2 if it required just one char of # the arg. # Current error values: # -1: Option that required an argument did not get it. # -2: Value of incorrect type supplied for option. # -3: Bad type given for option & function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum, SingleOpt,specGiven, UsedValue,Err,NumTypes) { # If option takes a value... [ NumTypes = "*()#<>]" if (Option == "&" && ArgType !~ "[" NumTypes) { # ] OptErr = "Bad type given for & option" return -3 } if (UsedValue = (ArgType ~ "[:;" NumTypes)) { # ] 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,specGiven)) != "") { OptErr = Err return -2 } # Mark this as a numeric variable; will be propogated to Options[] val. if (ArgType != ":" && ArgType != ";") Value += 0 if ((Instance = ++Options[Option,"count"]) > 1) Options[Option,Instance] = Value else Options[Option] = Value } # If this is an environ or rcfile assignment & it was given a value... else if (!OptionNum && Value != "") { UsedValue = 1 # If the value is "0" or "-" and this is the first instance of it, # do not set Options[Option]; this allows an assignment in an rcfile to # turn off an option (for the simple "Option in Options" test) in such # a way that it cannot be turned on in a later file. if (!(Option in Options) && (Value == "0" || Value == "-")) Instance = 1 else Instance = ++Options[Option] # Save the value even though this is a flag Options[Option,Instance] = Value } # If this is a command line flag and has a - following it in the same arg, # it is being turned off. else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") { UsedValue = 2 if (Option in Options) Instance = ++Options[Option] else Instance = 1 Options[Option,Instance] } # If this is a flag assignment without a value, increment the count for the # flag unless it was turned off. The indicator for a flag being turned off # is that the flag index has not been set in Options[] but it has an # instance count. else if (Option in Options || !((Option,1) in Options)) # Increment number of times this flag seen; will inc null value to 1 Instance = ++Options[Option] Options[Option,"num",Instance] = OptionNum 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 # ; Non-null string argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # specGiven is the option specifier character use, if any (e.g. - or +), # for use in error messages. # Returns null on success, err string on error function CheckType(ArgType,Value,Option,Name,specGiven, Err,ErrStr) { if (ArgType == ":") return "" if (ArgType == ";") { if (Value == "") Err = "must be a non-empty string" } # A number begins with optional + or -, and is followed by a string of # digits or a decimal with digits before it, after it, or both else 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 != "") { ErrStr = "Bad value \"" Value "\". Value assigned to " if (Name != "") return ErrStr "variable " substr(Name,1,1) " " Err else { if (Option == "&") Option = Value return ErrStr "option " specGiven substr(Option,1,1) " " Err } } else return "" } # 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. # rcFiles, if given, is a colon-seprated list of filenames to read for # variable initialization. If a filename begins with ~/, the ~ is replaced # by the value of the environment variable HOME. If a filename begins with # $, the part from the character after the $ up until (but not including) # the first character not in [a-zA-Z0-9_] will be searched for in the # environment; if found its value will be substituted, if not the filename will # be discarded. # rcfiles are read in the order given. # Values given in them will not override values given on the command line, # and values given in later files will not override those set in earlier # files, because AssignVal() will store each with a different instance index. # The first instance of each variable, either on the command line or in an # rcfile, will be stored with no instance index, and this is the value # normally used by programs that call this function. # 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 EnvSearch is given and nonzero, the first EnvSearch variables will also be # searched for in the environment. If set to -1, all values will be searched # for in the environment. Values given in the environment will override # those given in the rcfiles 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 rcfiles from being read. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and # ExclusiveOptions() for a description of exOpts. # 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 a positive integer value so it can be checked for in # an END block. # Return value: The number of elements left in ARGV is returned. # Must keep OptErr global since it may be set by InitOpts(). function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt, AllowUnrecOpt,optChars,exOpts, ArgsLeft,e) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt, optChars) if (ArgsLeft < (MinArgs+1) && !("h" in Options)) { if (ArgsLeft >= 0) { OptErr = "Not enough arguments" Err = 4 } else Err = -ArgsLeft printf "%s: %s.\nUse -h for help.\n%s\n", Name,OptErr,Usage > "/dev/stderr" exit 1 } if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) && (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0) { print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr" Err = -e exit 1 } if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != "")) { printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr" Err = 1 exit 1 } return ArgsLeft } # ReadConfFile(): Read a file containing var/value assignments, in the form # . # Whitespace (spaces and tabs) around a variable (leading whitespace on the # line and whitespace between the variable name and the assignment character) # is stripped. Lines that do not contain an assignment operator or which # contain a null variable name are ignored, other than possibly being noted in # the return value. If more than one assignment is made to a variable, the # first assignment is used. # Input variables: # File is the file to read. # Comment is the line-comment character. If it is found as the first non- # whitespace character on a line, the line is ignored. # Assign is the assignment string. The first instance of Assign on a line # separates the variable name from its value. # If StripWhite is true, whitespace around the value (whitespace between the # assignment char and trailing whitespace on the line) is stripped. # VarPat is a pattern that variable names must match. # Example: "^[a-zA-Z][a-zA-Z0-9]+$" # If FlagsOK is true, variables are allowed to be "set" by being put alone on # a line; no assignment operator is needed. These variables are set in # the output array with a null value. Lines containing nothing but # whitespace are still ignored. # Output variables: # Values[] contains the assignments, with the indexes being the variable names # and the values being the assigned values. # Lines[] contains the line number that each variable occured on. A flag set # is record by giving it an index in Lines[] but not in Values[]. # Return value: # If any errors occur, a string consisting of descriptions of the errors # separated by newlines is returned. In no case will the string start with a # numeric value. If no errors occur, the number of lines read is returned. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat, FlagsOK, Line,Status,Errs,AssignLen,LineNum,Var,Val) { if (Comment != "") Comment = "^" Comment AssignLen = length(Assign) if (VarPat == "") VarPat = "." # null varname not allowed while ((Status = (getline Line < File)) == 1) { LineNum++ sub("^[ \t]+","",Line) if (Line == "") # blank line continue if (Comment != "" && Line ~ Comment) continue if (Pos = index(Line,Assign)) { Var = substr(Line,1,Pos-1) Val = substr(Line,Pos+AssignLen) if (StripWhite) { sub("^[ \t]+","",Val) sub("[ \t]+$","",Val) } } else { Var = Line # If no value, var is entire line Val = "" } if (!FlagsOK && Val == "") { Errs = Errs \ sprintf("\nBad assignment on line %d of file %s: %s", LineNum,File,Line) continue } sub("[ \t]+$","",Var) if (Var !~ VarPat) { Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s", LineNum,File,Var) continue } if (!(Var in Lines)) { Lines[Var] = LineNum if (Pos) Values[Var] = Val } } if (Status) Errs = Errs "\nCould not read file " File close(File) return Errs == "" ? LineNum : substr(Errs,2) # Skip first newline } # Variables: # Data is stored in Options[]. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts(). # Global vars: # Sets OptErr. Uses ENVIRON[]. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch, Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile, fNames,numrcFiles,filesRead,Err,Values,retStr) { split("",filesRead,"") # make awk know this is an array NumVars = split(VarNames,Vars,",") TypesInd = Ret = 0 if (EnvSearch == -1) EnvSearch = NumVars for (i = 1; i <= NumVars; i++) { Var = Vars[i] CharOpt = substr(OptList,++TypesInd,1) if (CharOpt ~ "^[:;*()#<>&]$") CharOpt = substr(OptList,++TypesInd,1) Map[Var] = CharOpt Types[Var] = Type = substr(OptList,TypesInd+1,1) # Do not overwrite entries from environment if (i <= EnvSearch && Var in ENVIRON && (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0) return Err } numrcFiles = split(rcFiles,fNames,":") for (i = 1; i <= numrcFiles; i++) { rcFile = fNames[i] if (rcFile ~ "^~/") rcFile = ENVIRON["HOME"] substr(rcFile,2) else if (rcFile ~ /^\$/) { rcFile = substr(rcFile,2) match(rcFile,"^[a-zA-Z0-9_]*") envvar = substr(rcFile,1,RLENGTH) if (envvar in ENVIRON) rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1) else continue } if (rcFile in filesRead) continue # rcfiles are liable to be given more than once, e.g. UHOME and HOME # may be the same filesRead[rcFile] if ("x" in Options) printf "Reading configuration file %s\n",rcFile > "/dev/stderr" retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1) if (retStr > 0) READ_RCFILE = 1 else if (ret != "") { OptErr = retStr Ret = -1 } for (Var in Lines) if (Var in Map) { if ((Err = AssignVal(Map[Var], Var in Values ? Values[Var] : "",Options,Types[Var], Var in Values,Var,0)) < 0) return Err } else { OptErr = sprintf(\ "Unknown var \"%s\" assigned to on line %d\nof file %s",Var, Lines[Var],rcFile) Ret = -1 } } if ("x" in Options) for (Var in Map) if (Map[Var] in Options) printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \ "/dev/stderr" else printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr" return Ret } # OptSets is a semicolon-separated list of sets of option sets. # Within a list of option sets, the option sets are separated by commas. For # each set of sets, if any option in one of the sets is in Options[] AND any # option in one of the other sets is in Options[], an error string is returned. # If no conflicts are found, nothing is returned. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to # the exclusions presented by the first set of sets (ab,def,g) if: # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR # (a or b is in Options[]) AND (g is in Options) OR # (d, e, or f is in Options[]) AND (g is in Options) # An error will be returned due to the exclusions presented by the second set # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]). # todo: make options given on command line unset options given in config file # todo: that they conflict with. function ExclusiveOptions(OptSets,Options, Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets, SetNum,OSetNum) { NumSetSets = split(OptSets,SetSets,";") # For each set of sets... for (SetSet = 1; SetSet <= NumSetSets; SetSet++) { # NumSets is the number of sets in this set of sets. NumSets = split(SetSets[SetSet],Sets,",") # For each set in a set of sets except the last... for (SetNum = 1; SetNum < NumSets; SetNum++) { s1 = Sets[SetNum] L1 = length(s1) for (Pos1 = 1; Pos1 <= L1; Pos1++) # If any of the options in this set was given, check whether # any of the options in the other sets was given. Only check # later sets since earlier sets will have already been checked # against this set. if ((c1 = substr(s1,Pos1,1)) in Options) for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) { s2 = Sets[OSetNum] L2 = length(s2) for (Pos2 = 1; Pos2 <= L2; Pos2++) if ((c2 = substr(s2,Pos2,1)) in Options) ErrStr = ErrStr "\n"\ sprintf("Cannot give both %s and %s options.", c1,c2) } } } if (ErrStr != "") return substr(ErrStr,2) return "" } # The value of each instance of option Opt that occurs in Options[] is made an # index of Set[]. # The return value is the number of instances of Opt in Options. function Opt2Set(Options,Opt,Set, count) { if (!(Opt in Options)) return 0 Set[Options[Opt]] count = Options[Opt,"count"] for (; count > 1; count--) Set[Options[Opt,count]] return count } # The value of each instance of option Opt that occurs in Options[] that # begins with "!" is made an index of nSet[] (with the ! stripped from it). # Other values are made indexes of Set[]. # The return value is the number of instances of Opt in Options. function Opt2Sets(Options,Opt,Set,nSet, count,aSet,ret) { ret = Opt2Set(Options,Opt,aSet) for (value in aSet) if (substr(value,1,1) == "!") nSet[substr(value,2)] else Set[value] return ret } # Returns true if option Opt was given on the command line. function CmdLineOpt(Options,Opt, i) { for (i = 1; (Opt,"num",i) in Options; i++) if (Options[Opt,"num",i] != 0) return 1 return 0 } ### End of ProcArgs library ### Begin array routines # InitArr: Initialize an array with values. # Ind and Vals are separated into lists on Sep. # For each item in Ind, an index with that name is created in Arr[], # and the value with the same position in Vals is stored in it. # Global variables: none. function InitArr(Arr,Ind,Vals,sep, numind,indnames,values) { split(Ind,indnames,sep) split(Vals,values,sep) for (numind in indnames) Arr[indnames[numind]] = values[numind] } function ClearArr(Arr, Elem) { for (Elem in Arr) delete Arr[Elem] } function CopyArr(From,To, Elem) { for (Elem in From) To[Elem] = From[Elem] } # Subtract the values in Subtrahend from those in Minuend function SubtractArr(Minuend,Subtrahend, Elem) { for (Elem in Subtrahend) Minuend[Elem] -= Subtrahend[Elem] } # For each element of the array In, an element is created in Out having # an index equal to the value of the element in In and a value equal to # the index of the element in In. function Invert(In,Out, Index) { for (Index in In) Out[In[Index]] = Index } # Assign: make an array from a list of assignments. # An index with the name of each variable in the list is created in the array. # Its value is set to the value given for it. # Input variables: # Elements is a string containing the list of variable-value pairs. # Sep is the string that separates the pairs in the list. # AssignOp is the string that separates variables from values. # Output variables: # Arr is the array. # Return value: the number of elements added to the set. # Example: # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=") function Assign(Arr,Elements,Sep,AssignOp, Num,Names,Elem,Assignments,Assignment,i) { Num = split(Elements,Assignments,Sep) for (i = 1; i <= Num; i++) { Assignment = Assignments[i] Ind = index(Assignment,AssignOp) Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1) } return Num } # Packs Arr[], which should have integer indices starting at or above n, to # contiguous integer indices starting with n. # If n is not given it defaults to 0. # Num should be the number of elements in Arr. function PackArr(Arr,Num,n, NewInd,OldInd) { NewInd = OldInd = n+0 for (; Num; Num--) { while (!(OldInd in Arr)) OldInd++ if (NewInd != OldInd) { Arr[NewInd] = Arr[OldInd] delete Arr[OldInd] } OldInd++ NewInd++ } } ### End array routines ### Begin set library # 96/05/23 added return values jhdiii # 96/05/25 added set2list() # Return value: the number of new elements added to Inter function Intersection(A,B,Inter, Elem,Count) { for (Elem in A) if (Elem in B && !(Elem in Inter)) { Inter[Elem] Count++ } return Count } # Return value: the number of new elements added to Both function Union(A,B,Both) { return CopySet(A,Both) + CopySet(B,Both) } # Deletes any elements that are in both Minuend and Subtrahend from Minuend. # Return value: the number of elements deleted. function SubtractSet(Minuend,Subtrahend, Elem,nDel) { for (Elem in Subtrahend) if (Elem in Minuend) { delete Minuend[Elem] nDel++ } return nDel } # Return value: the number of new elements added to To function CopySet(From,To, Elem,n) { for (Elem in From) if (!(Elem in To)) { To[Elem] n++ } return n } # Returns 1 if Set is empty, 0 if not. function IsEmpty(Set, i) { for (i in Set) return 0 return 1 } # MakeSet: make a set from a list. # An index with the name of each element of the list is created in the given # array. # Input variables: # Elements is a string containing the list of elements. # Sep is the character that separates the elements of the list. # Output variables: # Set is the array. # Return value: the number of new elements added to the set. function MakeSet(Set,Elements,Sep, i,Num,Names,nFound,ind) { nFound = 0 Num = split(Elements,Names,Sep) for (i = 1; i <= Num; i++) { ind = Names[i] if (!(ind in Set)) { Set[ind] nFound++ } } return nFound } # Returns the number of elements in set Set function NumElem(Set, elem,Num) { for (elem in Set) Num++ return Num } # Remove all elements from Set function DeleteAll(Set, i) { split("",Set,",") } # Returns a list of all of the elements in Set[], with each pair of elements # separated by Sep. function set2list(Set,Sep, list,elem) { for (elem in Set) list = list Sep elem return substr(list,2) # skip 1st separator } ### End set library