### 4.4 Program and Arguments ¶

#### 4.4.1 Getopt ¶

`(require 'getopt)`

This routine implements Posix command line argument parsing. Notice that returning values through global variables means that `getopt` is not reentrant.

Obedience to Posix format for the `getopt` calls sows confusion. Passing argc and argv as arguments while referencing optind as a global variable leads to strange behavior, especially when the calls to `getopt` are buried in other procedures.

Even in C, argc can be derived from argv; what purpose does it serve beyond providing an opportunity for argv/argc mismatch? Just such a mismatch existed for years in a SLIB `getopt--` example.

I have removed the argc and argv arguments to getopt procedures; and replaced them with a global variable:

Variable: *argv* ¶

  Define *argv* with a list of arguments before calling getopt procedures. If you don’t want the first (0th) element to be ignored, set *optind* to 0 (after requiring getopt).

Variable: *optind* ¶

  Is the index of the current element of the command line. It is initially one. In order to parse a new command line or reparse an old one, *optind* must be reset.

Variable: *optarg* ¶

  Is set by getopt to the (string) option-argument of the current option.

Function: getopt optstring ¶

  Returns the next option letter in *argv* (starting from `(vector-ref argv *optind*)`) that matches a letter in optstring. *argv* is a vector or list of strings, the 0th of which getopt usually ignores. optstring is a string of recognized option characters; if a character is followed by a colon, the option takes an argument which may be immediately following it in the string or in the next element of *argv*.

  *optind* is the index of the next element of the *argv* vector to be processed. It is initialized to 1 by `getopt.scm`, and `getopt` updates it when it finishes with each element of *argv*.

  `getopt` returns the next option character from *argv* that matches a character in optstring, if there is one that matches. If the option takes an argument, `getopt` sets the variable *optarg* to the option-argument as follows:

  - If the option was the last character in the string pointed to by an element of *argv*, then *optarg* contains the next element of *argv*, and *optind* is incremented by 2. If the resulting value of *optind* is greater than or equal to `(length *argv*)`, this indicates a missing option argument, and `getopt` returns an error indication.
  - Otherwise, *optarg* is set to the string following the option character in that element of *argv*, and *optind* is incremented by 1.

  If, when `getopt` is called, the string `(vector-ref argv *optind*)` either does not begin with the character `#\-` or is just `"-"`, `getopt` returns `#f` without changing *optind*. If `(vector-ref argv *optind*)` is the string `"--"`, `getopt` returns `#f` after incrementing *optind*.

  If `getopt` encounters an option character that is not contained in optstring, it returns the question-mark `#\?` character. If it detects a missing option argument, it returns the colon character `#\:` if the first character of optstring was a colon, or a question-mark character otherwise. In either case, `getopt` sets the variable getopt:opt to the option character that caused the error.

  The special option `"--"` can be used to delimit the end of the options; `#f` is returned, and `"--"` is skipped.

  RETURN VALUE

  `getopt` returns the next option character specified on the command line. A colon `#\:` is returned if `getopt` detects a missing argument and the first character of optstring was a colon `#\:`.

  A question-mark `#\?` is returned if `getopt` encounters an option character not in optstring or detects a missing argument and the first character of optstring was not a colon `#\:`.

  Otherwise, `getopt` returns `#f` when all command line options have been parsed.

  Example:

      #! /usr/local/bin/scm
      (require 'program-arguments)
      (require 'getopt)
      (define argv (program-arguments))

      (define opts ":a:b:cd")
      (let loop ((opt (getopt (length argv) argv opts)))
        (case opt
          ((#\a) (print "option a: " *optarg*))
          ((#\b) (print "option b: " *optarg*))
          ((#\c) (print "option c"))
          ((#\d) (print "option d"))
          ((#\?) (print "error" getopt:opt))
          ((#\:) (print "missing arg" getopt:opt))
          ((#f) (if (< *optind* (length argv))
                    (print "argv[" *optind* "]="
                           (list-ref argv *optind*)))
                (set! *optind* (+ *optind* 1))))
        (if (< *optind* (length argv))
            (loop (getopt (length argv) argv opts))))

      (slib:exit)

#### 4.4.2 Getopt— ¶

Function: `getopt--` optstring ¶

  The procedure `getopt--` is an extended version of `getopt` which parses long option names of the form ‘`--hold-the-onions`’ and ‘`--verbosity-level=extreme`’. `Getopt--` behaves as `getopt` except for non-empty options beginning with ‘`--`’.

  Options beginning with ‘`--`’ are returned as strings rather than characters. If a value is assigned (using ‘`=`’) to a long option, `*optarg*` is set to the value. The ‘`=`’ and value are not returned as part of the option string.

  No information is passed to `getopt--` concerning which long options should be accepted or whether such options can take arguments. If a long option did not have an argument, `*optarg*` will be set to `#f`. The caller is responsible for detecting and reporting errors.

      (define opts ":-:b:")
      (define *argv* '("foo" "-b9" "--f1" "--2=" "--g3=35234.342" "--"))
      (define *optind* 1)
      (define *optarg* #f)
      (require 'qp)
      (do ((i 5 (+ -1 i)))
          ((zero? i))
        (let ((opt (getopt-- opts)))
          (print *optind* opt *optarg*)))
      -|
      2 #\b "9"
      3 "f1" #f
      4 "2" ""
      5 "g3" "35234.342"
      5 #f "35234.342"

#### 4.4.3 Command Line ¶

`(require 'read-command)`

Function: read-command port ¶
Function: read-command ¶

  `read-command` converts a command line into a list of strings suitable for parsing by `getopt`. The syntax of command lines supported resembles that of popular shells. `read-command` updates port to point to the first character past the command delimiter.

  If an end of file is encountered in the input before any characters are found that can begin an object or comment, then an end of file object is returned.

  The port argument may be omitted, in which case it defaults to the value returned by `current-input-port`.

  The fields into which the command line is split are delimited by whitespace as defined by `char-whitespace?`. The end of a command is delimited by end-of-file or unescaped semicolon (`;`) or `newline`. Any character can be literally included in a field by escaping it with a backslach (`\`).

  The initial character and types of fields recognized are:

  ‘`\`’

    The next character has is taken literally and not interpreted as a field delimiter. If `\` is the last character before a `newline`, that `newline` is just ignored. Processing continues from the characters after the `newline` as though the backslash and `newline` were not there.

  ‘`"`’

    The characters up to the next unescaped `"` are taken literally, according to [R4RS] rules for literal strings (see Strings in Revised(4) Scheme).

  ‘`(`’, ‘`%'`’

    One scheme expression is `read` starting with this character. The `read` expression is evaluated, converted to a string (using `display`), and replaces the expression in the returned field.

  ‘`;`’

    Semicolon delimits a command. Using semicolons more than one command can appear on a line. Escaped semicolons and semicolons inside strings do not delimit commands.

  The comment field differs from the previous fields in that it must be the first character of a command or appear after whitespace in order to be recognized. `#` can be part of fields if these conditions are not met. For instance, `ab#c` is just the field ab#c.

  ‘`#`’

    Introduces a comment. The comment continues to the end of the line on which the semicolon appears. Comments are treated as whitespace by `read-dommand-line` and backslashes before `newline`s in comments are also ignored.

Function: read-options-file filename ¶

  `read-options-file` converts an options file into a list of strings suitable for parsing by `getopt`. The syntax of options files is the same as the syntax for command lines, except that `newline`s do not terminate reading (only `;` or end of file).

  If an end of file is encountered before any characters are found that can begin an object or comment, then an end of file object is returned.

#### 4.4.4 Parameter lists ¶

`(require 'parameters)`

Arguments to procedures in scheme are distinguished from each other by their position in the procedure call. This can be confusing when a procedure takes many arguments, many of which are not often used.

A parameter-list is a way of passing named information to a procedure. Procedures are also defined to set unused parameters to default values, check parameters, and combine parameter lists.

A parameter has the form `(parameter-name value1 …)`. This format allows for more than one value per parameter-name.

A parameter-list is a list of parameters, each with a different parameter-name.

Function: make-parameter-list parameter-names ¶

  Returns an empty parameter-list with slots for parameter-names.

Function: parameter-list-ref parameter-list parameter-name ¶

  parameter-name must name a valid slot of parameter-list. `parameter-list-ref` returns the value of parameter parameter-name of parameter-list.

Function: remove-parameter parameter-name parameter-list ¶

  Removes the parameter parameter-name from parameter-list. `remove-parameter` does not alter the argument parameter-list.

  If there are more than one parameter-name parameters, an error is signaled.

Procedure: adjoin-parameters! parameter-list parameter1 … ¶

  Returns parameter-list with parameter1 … merged in.

Procedure: parameter-list-expand expanders parameter-list ¶

  expanders is a list of procedures whose order matches the order of the parameter-names in the call to `make-parameter-list` which created parameter-list. For each non-false element of expanders that procedure is mapped over the corresponding parameter value and the returned parameter lists are merged into parameter-list.

  This process is repeated until parameter-list stops growing. The value returned from `parameter-list-expand` is unspecified.

Function: fill-empty-parameters defaulters parameter-list ¶

  defaulters is a list of procedures whose order matches the order of the parameter-names in the call to `make-parameter-list` which created parameter-list. `fill-empty-parameters` returns a new parameter-list with each empty parameter replaced with the list returned by calling the corresponding defaulter with parameter-list as its argument.

Function: check-parameters checks parameter-list ¶

  checks is a list of procedures whose order matches the order of the parameter-names in the call to `make-parameter-list` which created parameter-list.

  `check-parameters` returns parameter-list if each check of the corresponding parameter-list returns non-false. If some check returns `#f` a warning is signaled.

In the following procedures arities is a list of symbols. The elements of `arities` can be:

`single`

  Requires a single parameter.

`optional`

  A single parameter or no parameter is acceptable.

`boolean`

  A single boolean parameter or zero parameters is acceptable.

`nary`

  Any number of parameters are acceptable.

`nary1`

  One or more of parameters are acceptable.

Function: parameter-list->arglist positions arities parameter-list ¶

  Returns parameter-list converted to an argument list. Parameters of arity type `single` and `boolean` are converted to the single value associated with them. The other arity types are converted to lists of the value(s).

  positions is a list of positive integers whose order matches the order of the parameter-names in the call to `make-parameter-list` which created parameter-list. The integers specify in which argument position the corresponding parameter should appear.

#### 4.4.5 Getopt Parameter lists ¶

`(require 'getopt-parameters)`

Function: getopt->parameter-list optnames arities types aliases desc … ¶

  Returns *argv* converted to a parameter-list. optnames are the parameter-names. arities and types are lists of symbols corresponding to optnames.

  aliases is a list of lists of strings or integers paired with elements of optnames. Each one-character string will be treated as a single ‘`-`’ option by `getopt`. Longer strings will be treated as long-named options (see getopt–).

  If the aliases association list has only strings as its `car`s, then all the option-arguments after an option (and before the next option) are adjoined to that option.

  If the aliases association list has integers, then each (string) option will take at most one option-argument. Unoptioned arguments are collected in a list. A ‘`-1`’ alias will take the last argument in this list; ‘`+1`’ will take the first argument in the list. The aliases -2 then +2; -3 then +3; … are tried so long as a positive or negative consecutive alias is found and arguments remain in the list. Finally a ‘`0`’ alias, if found, absorbs any remaining arguments.

  In all cases, if unclaimed arguments remain after processing, a warning is signaled and #f is returned.

Function: getopt->arglist optnames positions arities types defaulters checks aliases desc … ¶

  Like `getopt->parameter-list`, but converts *argv* to an argument-list as specified by optnames, positions, arities, types, defaulters, checks, and aliases. If the options supplied violate the arities or checks constraints, then a warning is signaled and #f is returned.

These `getopt` functions can be used with SLIB relational databases. For an example, See make-command-server.

If errors are encountered while processing options, directions for using the options (and argument strings desc …) are printed to `current-error-port`.

    (begin
      (set! *optind* 1)
      (set! *argv* '("cmd" "-?")
      (getopt->parameter-list
       '(flag number symbols symbols string flag2 flag3 num2 num3)
       '(boolean optional nary1 nary single boolean boolean nary nary)
       '(boolean integer symbol symbol string boolean boolean integer integer)
       '(("flag" flag)
         ("f" flag)
         ("Flag" flag2)
         ("B" flag3)
         ("optional" number)
         ("o" number)
         ("nary1" symbols)
         ("N" symbols)
         ("nary" symbols)
         ("n" symbols)
         ("single" string)
         ("s" string)
         ("a" num2)
         ("Abs" num3))))
    -|
    Usage: cmd [OPTION ARGUMENT ...] ...

      -f, --flag
      -o, --optional=<number>
      -n, --nary=<symbols> ...
      -N, --nary1=<symbols> ...
      -s, --single=<string>
          --Flag
      -B
      -a        <num2> ...
          --Abs=<num3> ...

    ERROR: getopt->parameter-list "unrecognized option" "-?"

#### 4.4.6 Filenames ¶

`(require 'filename)`

Function: filename:match?? pattern ¶
Function: filename:match-ci?? pattern ¶

  Returns a predicate which returns a non-false value if its string argument matches (the string) pattern, false otherwise. Filename matching is like glob expansion described the bash manpage, except that names beginning with ‘`.`’ are matched and ‘`/`’ characters are not treated specially.

  These functions interpret the following characters specially in pattern strings:

  ‘`*`’

    Matches any string, including the null string.

  ‘`?`’

    Matches any single character.

  ‘`[…]`’

    Matches any one of the enclosed characters. A pair of characters separated by a minus sign (-) denotes a range; any character lexically between those two characters, inclusive, is matched. If the first character following the ‘`[`’ is a ‘`!`’ or a ‘`^`’ then any character not enclosed is matched. A ‘`-`’ or ‘`]`’ may be matched by including it as the first or last character in the set.

Function: filename:substitute?? pattern template ¶
Function: filename:substitute-ci?? pattern template ¶

  Returns a function transforming a single string argument according to glob patterns pattern and template. pattern and template must have the same number of wildcard specifications, which need not be identical. pattern and template may have a different number of literal sections. If an argument to the function matches pattern in the sense of `filename:match??` then it returns a copy of template in which each wildcard specification is replaced by the part of the argument matched by the corresponding wildcard specification in pattern. A `*` wildcard matches the longest leftmost string possible. If the argument does not match pattern then false is returned.

  template may be a function accepting the same number of string arguments as there are wildcard specifications in pattern. In the case of a match the result of applying template to a list of the substrings matched by wildcard specifications will be returned, otherwise template will not be called and `#f` will be returned.

    ((filename:substitute?? "scm_[0-9]*.html" "scm5c4_??.htm")
     "scm_10.html")
    ⇒ "scm5c4_10.htm"
    ((filename:substitute?? "??" "beg?mid?end") "AZ")
    ⇒ "begAmidZend"
    ((filename:substitute?? "*na*" "?NA?") "banana")
    ⇒ "banaNA"
    ((filename:substitute?? "?*?" (lambda (s1 s2 s3) (string-append s3 s1)))
     "ABZ")
    ⇒ "ZA"

Function: replace-suffix str old new ¶

  str can be a string or a list of strings. Returns a new string (or strings) similar to `str` but with the suffix string old removed and the suffix string new appended. If the end of str does not match old, an error is signaled.

    (replace-suffix "/usr/local/lib/slib/batch.scm" ".scm" ".c")
    ⇒ "/usr/local/lib/slib/batch.c"

Function: call-with-tmpnam proc k ¶
Function: call-with-tmpnam proc ¶

  Calls proc with k arguments, strings returned by successive calls to `tmpnam`. If proc returns, then any files named by the arguments to proc are deleted automatically and the value(s) yielded by the proc is(are) returned. k may be ommited, in which case it defaults to `1`.

Function: call-with-tmpnam proc suffix1 … ¶

  Calls proc with strings returned by successive calls to `tmpnam`, each with the corresponding suffix string appended. If proc returns, then any files named by the arguments to proc are deleted automatically and the value(s) yielded by the proc is(are) returned.

#### 4.4.7 Batch ¶

`(require 'batch)`

The batch procedures provide a way to write and execute portable scripts for a variety of operating systems. Each `batch:` procedure takes as its first argument a parameter-list (see Parameter lists). This parameter-list argument parms contains named associations. Batch currently uses 2 of these:

`batch-port`

  The port on which to write lines of the batch file.

`batch-dialect`

  The syntax of batch file to generate. Currently supported are:

  - unix
  - dos
  - vms
  - amigaos
  - system
  - *unknown*

The ‘`batch`’ module uses 2 enhanced relational tables (see Using Databases) to store information linking the names of `operating-system`s to `batch-dialect`es.

Function: batch:initialize! database ¶

  Defines `operating-system` and `batch-dialect` tables and adds the domain `operating-system` to the enhanced relational database database.

Variable: *operating-system* ¶

  Is batch’s best guess as to which operating-system it is running under. `*operating-system*` is set to `(software-type)` (see Configuration) unless `(software-type)` is `unix`, in which case finer distinctions are made.

Function: batch:call-with-output-script parms file proc ¶

  proc should be a procedure of one argument. If file is an output-port, `batch:call-with-output-script` writes an appropriate header to file and then calls proc with file as the only argument. If file is a string, `batch:call-with-output-script` opens a output-file of name file, writes an appropriate header to file, and then calls proc with the newly opened port as the only argument. Otherwise, `batch:call-with-output-script` acts as if it was called with the result of `(current-output-port)` as its third argument.

The rest of the `batch:` procedures write (or execute if `batch-dialect` is `system`) commands to the batch port which has been added to parms or `(copy-tree parms)` by the code:

    (adjoin-parameters! parms (list 'batch-port port))

Function: batch:command parms string1 string2 … ¶

  Calls `batch:try-command` (below) with arguments, but signals an error if `batch:try-command` returns `#f`.

These functions return a non-false value if the command was successfully translated into the batch dialect and `#f` if not. In the case of the `system` dialect, the value is non-false if the operation suceeded.

Function: batch:try-command parms string1 string2 … ¶

  Writes a command to the `batch-port` in parms which executes the program named string1 with arguments string2 ….

Function: batch:try-chopped-command parms arg1 arg2 … list ¶

  breaks the last argument list into chunks small enough so that the command:

      arg1 arg2 ... chunk

  fits withing the platform’s maximum command-line length.

  `batch:try-chopped-command` calls `batch:try-command` with the command and returns non-false only if the commands all fit and `batch:try-command` of each command line returned non-false.

Function: batch:run-script parms string1 string2 … ¶

  Writes a command to the `batch-port` in parms which executes the batch script named string1 with arguments string2 ….

  Note: `batch:run-script` and `batch:try-command` are not the same for some operating systems (VMS).

Function: batch:comment parms line1 … ¶

  Writes comment lines line1 … to the `batch-port` in parms.

Function: batch:lines->file parms file line1 … ¶

  Writes commands to the `batch-port` in parms which create a file named file with contents line1 ….

Function: batch:delete-file parms file ¶

  Writes a command to the `batch-port` in parms which deletes the file named file.

Function: batch:rename-file parms old-name new-name ¶

  Writes a command to the `batch-port` in parms which renames the file old-name to new-name.

In addition, batch provides some small utilities very useful for writing scripts:

Function: truncate-up-to path char ¶
Function: truncate-up-to path string ¶
Function: truncate-up-to path charlist ¶

  path can be a string or a list of strings. Returns path sans any prefixes ending with a character of the second argument. This can be used to derive a filename moved locally from elsewhere.

      (truncate-up-to "/usr/local/lib/slib/batch.scm" "/")
      ⇒ "batch.scm"

Function: string-join joiner string1 … ¶

  Returns a new string consisting of all the strings string1 … in order appended together with the string joiner between each adjacent pair.

Function: must-be-first list1 list2 ¶

  Returns a new list consisting of the elements of list2 ordered so that if some elements of list1 are `equal?` to elements of list2, then those elements will appear first and in the order of list1.

Function: must-be-last list1 list2 ¶

  Returns a new list consisting of the elements of list1 ordered so that if some elements of list2 are `equal?` to elements of list1, then those elements will appear last and in the order of list2.

Function: os->batch-dialect osname ¶

  Returns its best guess for the `batch-dialect` to be used for the operating-system named osname. `os->batch-dialect` uses the tables added to database by `batch:initialize!`.

Here is an example of the use of most of batch’s procedures:

    (require 'databases)
    (require 'parameters)
    (require 'batch)
    (require 'filename)

    (define batch (create-database #f 'alist-table))
    (batch:initialize! batch)

    (define my-parameters
      (list (list 'batch-dialect (os->batch-dialect *operating-system*))
            (list 'operating-system *operating-system*)
            (list 'batch-port (current-output-port)))) ;gets filled in later

    (batch:call-with-output-script
     my-parameters
     "my-batch"
     (lambda (batch-port)
       (adjoin-parameters! my-parameters (list 'batch-port batch-port))
       (and
        (batch:comment my-parameters
                       "================ Write file with C program.")
        (batch:rename-file my-parameters "hello.c" "hello.c~")
        (batch:lines->file my-parameters "hello.c"
                           "#include <stdio.h>"
                           "int main(int argc, char **argv)"
                           "{"
                           "  printf(\"hello world\\n\");"
                           "  return 0;"
                           "}" )
        (batch:command my-parameters "cc" "-c" "hello.c")
        (batch:command my-parameters "cc" "-o" "hello"
                      (replace-suffix "hello.c" ".c" ".o"))
        (batch:command my-parameters "hello")
        (batch:delete-file my-parameters "hello")
        (batch:delete-file my-parameters "hello.c")
        (batch:delete-file my-parameters "hello.o")
        (batch:delete-file my-parameters "my-batch")
        )))

Produces the file `my-batch`:

    #! /bin/sh
    # "my-batch" script created by SLIB/batch Sun Oct 31 18:24:10 1999
    # ================ Write file with C program.
    mv -f hello.c hello.c~
    rm -f hello.c
    echo '#include <stdio.h>'>>hello.c
    echo 'int main(int argc, char **argv)'>>hello.c
    echo '{'>>hello.c
    echo '  printf("hello world\n");'>>hello.c
    echo '  return 0;'>>hello.c
    echo '}'>>hello.c
    cc -c hello.c
    cc -o hello hello.o
    hello
    rm -f hello
    rm -f hello.c
    rm -f hello.o
    rm -f my-batch

When run, `my-batch` prints:

    bash$ my-batch
    mv: hello.c: No such file or directory
    hello world