[comp.os.vms] Wild card expansion under VAX11-C: Code Wanted

woo@pioneer.arpa (Alex Woo) (03/01/88)

How does one expand wildcards in command line arguments in
VAX11-C under VMS?  It is possible to write a *.CLD and use
DCL but there should be similar services callable from C.
If you have a code fragment which which does this, please
either post it or mail me a copy.

Thanks.

Alex

ERICMC@USU.BITNET (Eric McQueen) (03/04/88)

In article <5361@ames.arpa> woo@pioneer.UUCP (Alex Woo) writes:
> How does one expand wildcards in command line arguments in
> VAX11-C under VMS?

I have a tool for C programmers using VMS, especially those porting facilities
from Un*x.  It prompts for command line arguments (unless you have already
specified some via a "foreign command" or MCR), expands file wildcards found on
the command line, and redirects `stdin' or `stdout'.  Here's a sample use:

main( argc, argv )
  int argc;
  char **argv;
{
  /* local variables */
#ifdef VAXC     /* Or whatever the ANSI-X3J11-conforming word will be */
  extern char **cmd_lin();
        argv = cmd_lin( "", argc, argv );
        /* The first argument provides misc. options (not currenly used). */
#endif
        /* your routine */
}

ERICMC@USU.BITNET (Eric McQueen) (03/04/88)

(sorry for the previous abbreviated copy of this)

In article <5361@ames.arpa> woo@pioneer.UUCP (Alex Woo) writes:
> How does one expand wildcards in command line arguments in
> VAX11-C under VMS?

I have a tool for C programmers using VMS, especially those porting facilities
from Un*x.  It prompts for command line arguments (unless you have already
specified some via a "foreign command" or MCR), expands file wildcards found on
the command line, and redirects `stdin' or `stdout'.  Here's a sample use:

main( argc, argv )
  int argc;
  char **argv;
{
  /* local variables */
#ifdef VAXC     /* Or whatever the ANSI-X3J11-conforming word will be */
  extern char **cmd_lin();
        argv = cmd_lin( "", argc, argv );
        /* The first argument provides misc. options (not currenly used). */
#endif
        /* your routine */
}

$ run grep
_command_line_arguments: -i STOP *.for

The source is 40 blocks (600 lines) so I'm not sure that I should post the
actual source in this group, but I have (see below).  If you are interested,
please look through the documentation, try it out, and send any significant
comments, suggestions, useful peices of code, etc. to one of the addresses
below.  I'll post summaries to comp.os.vms/INFO-VAX and comp.lang.c/INFO-C.
Please excuse the "lived-in" look of the code but I'm still working on this
thing so this is mearly the last debugged version I have.

---
Eric Tye McQueen          Mathematics Department        Also at (after some
ericmc@usu.bitnet         Utah State University       time in March[June?!]):
 (801) 753-4683           Logan, Utah  84322-3900       ericmc@usu.usu.edu

   UUCP:  ...{psuvax1,uunet}!usu.bitnet!ericmc       "Doodle doodle dee
   Arpa:  ericmc%usu.bitnet@cunyvm.cuny.edu           wubba wubba wubba."

+---------------------------------- cmdlin.c ----------------------------------+
/* cmdlin.c -- Un*x-sytle command line processing for VMS.  (V2.0,  3-Mar-1988)
 *      Copyright (C) 1988 Eric Tye McQueen
 * Emulates Un*x-style command line options in VAX C programs:
 *    - Allows entry of command line arguments after the program starts:
 *      The easiest way to run a program in VMS (the RUN command) does not
 *      allow for specification of command-line arguments while other methods
 *      that do allow arguments won't interpret the arguments in a very Un*x-
 *      like manner.  If no arguments are specified (other than the program
 *      name which is supplied by VAX C), we prompt for arguments from
 *      SYS$COMMAND and try to interpret them as Un*x would.
 *    - Expands any filename wildcards found in the command line arguments:
 *      C programs that process files are usually written to accepts several
 *      filenames, one name per argument.  Un*x environments expand filename
 *      wildcards specified on the command line to the names of all matching
 *      files and pass these names to the program.  This routine does this
 *      since VMS will not.  VMS wildcards and filenames are supported, NOT
 *      Un*x-style wildcards and filenames (this has its good and bad points).
 *    - Supports simple C-shell-style standard I/O redirection (`<', `>', `>>',
 *      `>&', and `>>&').
 *    - In the future support can be added for strings and quoting of charac-
 *      ters (`\', "'", `"'), symbol substitution (`$'), parallel processing
 *      and pipes (`||', `&&', etc.), etc.
 *
 * Currently supported:
 *      All VMS wildcards (`*', `%', and `...').
 *      Both VMS and Un*x-style file names.
 *      I/O redirection via `<', `>', `>>', `>&', and `>>&'
 *          (watch out for people who use `<' and `>' for directory names)
 * Reasonable to add support for:
 *      Specification of cmd_lin() options between `{' and `}'.
 *      Continuation lines via \ at end of line.
 *      Inclusion of spaces (and newlines) into arguments via ' and ".
 *      Substitutions from the VMS symbol table using ${name}.
 *      Using Un*x-style directories (/) with wildcards.
 * Pipe dreams:
 *      Pipes via || and &&.
 *      Un*x-style wildcards like [char-set].
 *          (watch out for people who use `[' and `]' for directory names!!)
 *
 * This files also contains a special demonstartion program.  To use it type:
 *      $ cc cmdlin/define:example
 *      $ link cmdlin,sys$input:/opt
 *      sys$share:vaxcrtl/share
 *      $ run cmdlin
 *
 * Sample use:
 */     /* grep.c
 *       * (with support for VAX C)
 *       */
/*      ...
 *      main( argc, argv )
 *        int argc;
 *        char **argv;
 *      {
 *        ... (definitions)
 *      #ifdef  VAXC
 *        extern char **cmd_lin();
 *              argv = cmd_lin( "", &argc, argv );
 *      #endif
 *              ... (other statements)
 *      }
 *      ...
 *      $ run grep
 *      _command_line_arguments: -i stop *.for
 *
 *      If you specify something that looks like a VMS wildcard (that contains
 * "*", "%", or "...") but that doesn't match the name of any existing files,
 * cmd_lin() will write "<pattern>: No match." to `stderr'.  If you specify a
 * string containing "*", "%", or "..." that is not a valid VMS file wildcard,
 * cmd_lin() will also write "<pattern>: Invalid file wildcard." to `stderr'.
 * In either case, the original string will included, unchanged, as a command
 * line argument.
 */

static char version[] = /* (Not currently) printed inside "debug" option. */
  "cmd_lin() V2.0, Copyright (C) 1988 Eric Tye McQueen (3-Mar).";

#ifndef VAXC    /* Should be changed to ANSI-conforming name soon. */

/* Dummy version of cmd_lin() for non VAX C (nonVMS) systems: */

char **
cmd_lin( aargc, argv )
  int *aargc;
  char **argv;
{
        return( argv );
}

#else /* VAXC */

#include <ctype.h>      /* iscntrl() isascii() */
#include <errno.h>      /* errno vaxc$errno */
#include <stdio.h>      /* stdin stdout stderr */
/* These should be changed when old versions of VAX C become rarer: */
extern char *strcpy(), *strchr(), *strrchr();
extern void *malloc(), *realloc(), free();
extern int strlen();

typedef  char  bool;            /* Smallest addressable signed data type. */
typedef  unsigned char   uchar;
typedef  unsigned short  ushort;
typedef  unsigned int    uint;
typedef  unsigned long   ulong;

#define   odd(stat)   ( (stat) & 1 )

/* General VMS descriptor (not as clumsy as $DESCRIPTOR from <descrip.h>): */
struct descr {
        ushort leng;    /* Length of data area (or string). */
        uchar  type;    /* Type of data in area (dsc$k_dtype_t). */
        uchar  class;   /* Class of desctiptor (dsc$k_class_s). */
        char  *addr;    /* Address of start of data area. */
};
globalvalue dsc$k_dtype_t; /* Text (data type) */
globalvalue dsc$k_class_s; /* Static (class) descriptor */

/* To allocate a descriptor (dsc) for array of char (arr): */
#define   desc_arr(dsc,arr)     struct descr dsc = \
  { (sizeof arr)-1, dsc$k_dtype_t, dsc$k_class_s, arr }

/* To allocate a descriptor (dsc) for null-terminated string (str): */
#define   desc_str(dsc,str)     struct descr dsc = \
  { strlen(str), dsc$k_dtype_t, dsc$k_class_s, str }

#define   FNAMSIZ   256 /* size of filename buffers (+1 for '\0') */
#define   LINSIZ   4096 /* maximum length of command line */

/* Signals any non-boring condition values so that error/warning messages
 * will be displayed. */
void
sigvms( stat )
  uint stat;
{
  extern void lib$signal();
        if(  ( stat & 0xffff ) == 1 )
                return;         /* Boring status message; skip it. */
        lib$signal( stat );
}

/* Returns the position of a substring within a string.  Should be replaced by
 * strstr() when DEC provides one. */
char *
strsub( str, sub )
  char *str, *sub;
{
  register char *fp = sub;      /* Points to character we hope to Find next */
  char *sp = strchr(str,*fp);   /* Points to current Starting character */
  register char *cp = sp;       /* Points to character currently Comparing */
        while(  sp  ) {
                while(  *fp  &&  *fp == *cp  )
                        ++fp, ++cp;
                if(  !*fp  )
                        return( sp );   /* We found it */
                fp = sub;               /* Start the search over again */
                cp = sp = strchr(sp+1,*fp);     /* were we left off last time */
        }
        return( 0 );
}

/* Creates a logical name. */
uint                            /* Returns a VMS condition value. */
crelnm( table, name, value )
  char *table, *name, *value;
{
  desc_str( tdsc, table );
  desc_str( ndsc, name );
  struct itemstr {
    ushort buflen, type;
    char *addr;
    ushort *retlen;
    uint end;
  } itemrec;
  /* globalvalue psl$k_user; /* Change this to "#include psldef". */
  uchar mode = 3 /* psl$k_user */;
  extern uint sys$crelnm();
  /* globalvalue lnm$_string; /* Change this to "#include lnmdef". */
        itemrec.type    = 2 /* lnm$_string */;
        itemrec.buflen  = strlen(value);
        itemrec.addr    = value;
        itemrec.retlen  = (ushort *) 0;
        itemrec.end     = 0;
        return(  sys$crelnm( 0, &tdsc, &ndsc, &mode, &itemrec )  );
}

/* Same as malloc() except never returns NULL. */
void *
emalloc( siz )
  unsigned siz;
{
  globalvalue ss$_insfmem;      /* Insufficient dynamic memory error */
  register void *p = malloc( siz );
        if(  !p  )      /* Don't try to recover after allocating too much: */
                sys$exit( ss$_insfmem );        /* Don't recover */
        return( p );
}

/* Same as realloc() except never returns NULL. */
void *
erealloc( ptr, siz )
  void *ptr;
  unsigned siz;
{
  globalvalue ss$_insfmem;      /* Insufficient dynamic memory error */
  register void *p = realloc( ptr, siz );
        if(  !p  )      /* Don't try to recover after allocating too much: */
                sys$exit( ss$_insfmem );        /* Don't recover */
        return( p );
}

static bool     /* Returns 1 if `name' looks like a wild-carded file name. */
iswild( name )
  char *name;
{
        return(
          strchr(name,'*')  ||  strchr(name,'%')  ||  strsub(name,"...")
        );
}

/* Returns the next file name matching the specified wildcard. */
static char *   /* Returns a pointer to an malloc()ed expanded filename */
nxt_wld( wild ) /* Returns 0 if all matching filenames have been returned. */
  char  *wild;  /* Wildcard to be expanded. */
{
  desc_str( wlddsc, wild );     /* VMS descriptor of wildcard string. */
  static uint cntxt = 0;        /* Context of wildcard search. */
  uint stat;                    /* Status returned by lib$* services. */
  uint two = 2;                 /* Flags to be passed by reference. */
  char file[FNAMSIZ];           /* Buffer to hold expanded file name. */
  desc_arr( fildsc, file );     /* VMS descriptor of buffer. */
  extern uint lib$find_file(), lib$find_file_end();     /* Library services. */
  globalvalue rms$_fnf, rms$_nmf, rms$_syn;     /* Possible statuses. */
        stat = lib$find_file( &wlddsc, &fildsc, &cntxt, 0, 0, 0, &two );
        if(  rms$_syn == stat  ) {      /* File syntax error: */
                fprintf( stderr, "%s: Invalid file wildcard.\n", wild );
        } else if(  rms$_fnf != stat  &&  rms$_nmf != stat  ) {
                /* Not "file not found" (1st try)
                 * nor "no more files" (later tries): */
                sigvms( stat ); /* Display non-trivial status messages. */
        }
        if(  !odd(stat)  ) {    /* Search didn't work: */
                sigvms(  lib$find_file_end( &cntxt )  );
                return( 0 );
        }
        while(  ' ' == file[fildsc.leng-1]  )   /* Remove trailing spaces: */
                --fildsc.leng;
        file[fildsc.leng] = '\0';
        /* Make copy of expanded name suitable for `argv': */
        return(  strcpy( emalloc((unsigned)fildsc.leng+1), file )  );
}

/* Prompt for a string from SYS$INPUT: */
char *
read_w_prompt( prompt, buff, size )
  char *prompt; /* String to prompt user with */
  char *buff;   /* Pointer to buffer to store the string that is read */
  int size;     /* Size of *buff */
{
  desc_str( pdsc, prompt );
  struct descr bdsc = { size-1, dsc$k_dtype_t, dsc$k_class_s, buff };
  extern uint lib$get_command();
  ushort len;
  uint stat = lib$get_command( &bdsc, &pdsc, &len );
        if(  !odd( stat )  ) {
                errno = EIO; /* or EVMSERR? */
                vaxc$errno = stat;
                return( 0 );
        }
        buff[len] = '\0';
        return( buff );
}

/* Builds argc,argv type lists of strings: */
static char **
add_arg( arg, acnt, ptrs )
  char *arg;    /* Argument to be added to the argument list */
  int *acnt;    /* Number of arguments currently in the argument list */
  char **ptrs;  /* Array of pointers to the arguments */
{
# define ARGRP 64       /* number of string pointers allocated at a time */
        if(  !ptrs  ) { /* Initialize a new argument list: */
          char **v = emalloc( ARGRP * sizeof(char *) );
                *acnt = 0;
                v[0] = 0;
                return( v );
        }
        if(  0 == (*acnt+1) % ARGRP  ) /* Need more space for pointers */
                ptrs = erealloc( ptrs, (*acnt+1+ARGRP) * sizeof(char *) );
        ptrs[*acnt] = arg;
        ptrs[++*acnt] = (char *) 0;
        return( ptrs );
}

/* Usually used to simplify argv[0]. */
char *
nameonly( file )
  char *file;
{
  char *cp = file, *tp;
#ifdef VAXC
        tp = strrchr(cp,']');           /* Skip directory */
        tp = tp ? tp+1 : cp;
        cp = strrchr(tp,'>');           /* Skip alternate form of directory */
        cp = cp ? cp+1 : tp;
        tp = strrchr(cp,':');           /* Skip device and/or node */
        tp = tp ? tp+1 : cp;
        cp = tp;
        if(  tp = strchr(cp,';')  )     /* Remove version number */
                *tp = '\0';
        if(  tp = strchr(cp,'.')  )     /* Remove extension and/or... */
                *tp = '\0';             /*  alternate form for version */
#else /* VAXC */
        /* cp = strrchrb(cp,'/'); may be useful on other than MS-DOS */
#ifdef MSDOS    /* Like this would ever be run under MS-DOS! */
        tp = strrchr(cp,'/');           /* Skip Un*x path */
        tp = tp ? tp+1 : cp;
        cp = strrchr(tp,'\\');          /* Skip MS-DOS path */
        cp = cp ? cp+1 : tp;
        if(  tp = strchr(cp,'.')  )
                *tp = '\0';             /* Remove extension */
#endif /* MSDOS */
#endif /* VAXC */
        return( cp );
}

/* Options that are set by an argument like "{opt1,!opt2,noopt3,opt4=val}":
 * (none of these are really supported in this release)
 *      help : show possible options then terminate execution.
 *      option : allows options to be specified (default).
 *      expand : expand VMS-style wildcards (default).
 *      prompt [ = string ] : prompt if no arguments are present (default).
 *      append : prompt if some arguments are initially present.
 *      debug : show option changes and final command line arguments.
 *      redirect : allows `<', `>', etc. to be used to redirect `std*'.
 *      unixy : don't return VMS-style device or directory names (default).
 *      lower : convert letters in file specifications to lowercase (default).
 *      device = "never", "different" (default), "always" : show device name.
 *      directory = "never", "different" (default), "always" : show directory.
 *      version = "never", "different" (default), "always" : show version.
 *      `dev':/`path' : Un*x-like alias for device name (ex: "user$disk:/usr").
 * Interactions:
 *      unixy & show device -> show directory
 *      unixy & !device & directory -> directory is relative (../etc)
 *      unixy & !device & directory=always -> default = ./
 */

/* Key values for OTfreq options (device, directory, and version): */
enum { never=0, diff, always };

/* Current values of all potions: */
char    *OPprompt       = "_command_line_arguments: ";
char    *OPoption       = "{}";
bool     OPexpand       = 1;
bool     OPappend       = 0;
bool     OPdebug        = 0;
bool     OPredir        = 1;
bool     OPunixy        = 1;
bool     OPlower        = 1;
bool     OPdevice       = diff;
bool     OPdirect       = diff;
bool     OPversion      = diff;

/* Accepts argument beginning "{" and parses the options listed before the
 * closing "}". */
static char **          /* Returns new value of `inpv'. */
parse_opt( ainpc, inpv )
  int *ainpc;
  char **inpv;
{
        /* code to be added later */
        return( inpv );
}

/* Read additional command line arguments: */
static char **
read_cmd_lin( ainpc, inpv )
  int *ainpc;
  char **inpv;
{
  int midc;
  char **midv, line[4096], *ap, *cp, c;
        if(  NULL == read_w_prompt( OPprompt, line, sizeof(line) )  )
                lib$stop( vaxc$errno );
        midv = add_arg( (char *) 0, &midc, (char *) 0 );
        while(  (*ainpc)--  )
                midv = add_arg( *(inpv++), &midc, midv );
        cp = strcpy(  emalloc( strlen(line)+1 ),  line  );
        do {
                while(  isspace( *cp )  )
                        cp++;
                if(  !*( ap = cp )  )
                        break;
                while(  *cp  &&  !isspace(*cp)  )
                        cp++;
                c = *cp;   *cp++ = '\0';
                midv = add_arg( ap, &midc, midv );
        } while(  c  );
        *ainpc = midc;
        return( midv );
}

/* Aspects of redirection common to both ">" (and variants) and "<": */
char **                         /* Returns new value for `inpv'. */
redir( ainpc, inpv, fp, io, tok, lnm, acc )
  int *ainpc;
  char **inpv;
  FILE *fp;     /* `stdin' or `stdout' */
  char *io;     /* "in" or "out" */
  char *tok;    /* ", `<'" or " (`>', `>>', `>&', or `>>&')" */
  char *lnm;    /* logical name:  "SYS$INPUT:" or "SYS$OUTPUT:" */
  char *acc;    /* file access:  "r", "w", or "a" */
{
  uint stat = 0;        /* Assume we wouldnn't reassign `lnm'. */
        if(  !**inpv  )         /* Not ">file" / "<file", must be... */
                --*ainpc,  ++inpv;              /* ... "> file" / "< file". */
        if(  !*inpv  ||  !**inpv  ) {
                fprintf( stderr,
                  "Invalid null %sput redirection%s.\n", io, tok );
                if(  *ainpc == 0  )     /* Fell off end of argument list: */
                        ++*ainpc,  --inpv;
                return( inpv );
        }
        if(  strchr( *inpv, '*' )  ||  strchr( *inpv, '%' )
         ||  strsub( *inpv, "..." )  ) {
                fprintf( stderr, "Wildcards (%s) illegal in redirection%s.\n",
                  *inpv, tok );
                return( inpv );
        }
        *strchr( lnm, ':' ) = '\0';     /* Remove trailing colon. */
        if(  strchr( *inpv, '/' )  ||  strsub( *inpv, ".." )  ) {
                if(  OPdebug  )
                        fprintf( stderr, "%s %s %s (%s).\n",
                         "Warning:  Can't reassign", lnm,
                         "to Un*x-style file name", *inpv );
        } else {
                stat = crelnm( "LNM$FILE_DEV", lnm, *inpv );
                if(  OPdebug  )
                        sigvms( stat );
        }
        lnm[strlen(lnm)] = ':';         /* Restore trailing colon. */
        if(  !freopen( odd(stat) ? lnm : *inpv, acc, fp )  ) {
        /* if(  !( odd(stat) && freopen(lnm,acc,fp) )
         &&  !freopen(*inpv,acc,fp)  ) { */
                fprintf( stderr, "Can't open \"%s\" as `std%s'.\n", *inpv, io );
                /* perror( *inpv ); */
                exit( vaxc$errno );
        }
        return( inpv );
}

/* Redirects `stdin': */
static char **
redirin( ainpc, inpv )
  int *ainpc;
  char **inpv;
{
        ++*inpv;        /* Skip over the '<'. */
        return(
          redir( ainpc, inpv, stdin, "in", ", `<'", "SYS$INPUT:", "r" )
        );
}

/* Redirects `stdout': */
static char **
redirout( ainpc, inpv )
  int *ainpc;
  char **inpv;
{
  bool err = 0;         /* Assume not ">&" nor ">>&". */
  char *acc = "w";      /* Assume ">" (write) not ">>" (append). */
        if(  '>' == *++*inpv  )         /* Append to output file: */
                ++*inpv,  *acc = 'a';
        if(  '&' == **inpv  )           /* Redirect `stderr' as well: */
                ++*inpv,  err = 1;
        inpv = redir( ainpc, inpv, stdout, "out",
          " (`>', `>>', `>&', or `>>&')", "SYS$OUTPUT:", acc );
        /* The following is harmless if redir() failed: */
        if(  err  ) {
                if(  !strchr( *inpv, '/' )  &&  !strsub( *inpv, ".." )  ) {
                  uint stat = crelnm( "LNM$FILE_DEV", "SYS$ERROR", *inpv );
                        if(  OPdebug  )
                                sigvms( stat );
                }
                stderr = stdout;        /* VAX-C specific method. */
        }
        return( inpv );
}

char **         /* returns new value for main\argv */
cmd_lin( opt0, ainpc, inpv )
  char  *opt0;  /* Options specified in `main()' */
  int   *ainpc; /* &main\argc (input and output) */
  char  **inpv; /* main\argv (input) */
{
  int endc;     /* `endc' and `endv' will be `argc' and `argv' for main() */
  char **endv = add_arg( 0, &endc, 0 ); /* Initialize `add_arg()' structures */
  char **xtra = 0;      /* So we can free() what read_cmd_lin() may allocate */
  char *cp;
        if(  OPoption[0] == *opt0  )
                inpv = parse_opt( ainpc, inpv );
#ifdef DEBUG
        else if(  *opt0  ) {
          globalvalue ss$abort;
                fprintf( stderr, "%s (%s) %s.\n", "Invalid options string",
                  opt0, "specified in `main()'" );
                exit( ss$abort );
        }
#endif /* DEBUG */
        if(  *ainpc  ) {        /* Process `argv[0]', the program name: */
                if(  OPunixy  ) /* Remove VMS device/dir/version: */
                        *inpv = nameonly( *inpv );      /* (and ".ext") */
                endv = add_arg( *inpv, &endc, endv );
                --*ainpc;  ++inpv;
        } else  /* (Impossible?)  No `argv[0]' so make one up: */
                endv = add_arg( "Me", &endc, endv );
        if(  OPappend  ||  ( !*ainpc && OPprompt )  )
                xtra = inpv = read_cmd_lin( ainpc, inpv );
        while(  *ainpc  ) {
                if(  OPoption  &&  OPoption[0] == **inpv  ) {
                        inpv = parse_opt( ainpc, inpv );
                } else if(  OPredir  &&  '<' == **inpv  ) {
                        inpv = redirin( ainpc, inpv );
                } else if(  OPredir  &&  '>' == **inpv  ) {
                        inpv = redirout( ainpc, inpv );
                } else if(  OPexpand  &&  iswild( *inpv )  ) {
                        if(  ( cp = nxt_wld(*inpv) )  ) {
                                do {
                                        endv = add_arg( cp, &endc, endv );
                                } while(  ( cp = nxt_wld(*inpv) )  );
                        } else {
                                fprintf( stderr, "%s: No match.\n", *inpv );
                                endv = add_arg( *inpv, &endc, endv );
                        }
                } else
                        endv = add_arg( *inpv, &endc, endv );
                --*ainpc;  ++inpv;
        }
        if(  xtra  )    /* An extra block of pointers was malloc()ed */
                free( xtra );
        *ainpc = endc;
        return( endv );
}

#ifdef EXAMPLE

/* Displays a string to `stdout', making all control and meta characters
 * printable unambigously. */
void
dump( str )
  char *str;
{
# define META '`'       /* Show 'a'+'\200' as "`a" */
# define CTRL '^'       /* Show '\001' as "^A" */
# define BOTH '~'       /* Show '\201' as "~A" */
# define QOTE '~'       /* Show '`' as "~`", '^' as "~^", and '~' as "~~" */
# define DEL '\177'
# define uncntrl(c)     ( DEL==c ? '?' : c + '@' )
  static char spec[] = { META, CTRL, BOTH, QOTE, 0 };
        while(  *str  ) {
                switch(  (!!iscntrl(*str)) + 2 * (!isascii(*str))  ) {
                  case 0: /* Normal */
                        if(  strchr( spec, *str )  )
                                putchar( QOTE );
                        putchar( *str );
                        break;
                  case 1: /* Control */
                        putchar( CTRL );
                        putchar( uncntrl(*str) );
                        break;
                  case 2: /* Meta */
                        putchar( META );
                        putchar( toascii(*str) );
                        break;
                  case 3: /* Both */
                        putchar( BOTH );
                        putchar( uncntrl(toascii(*str)) );
                        break;
                }
                ++str;
        }
}

main( argc, argv )
  int argc;
  char **argv;
{
  int cnt;
  extern char **cmd_lin();
        argv = cmd_lin( "", &argc, argv );
        printf( "%d argument%s%s\n", argc,
          1==argc ? "" : "s", argc ? ":" : "." );
        for(  cnt = 0;  *argv;  ++argv  ) {
                printf( "%5d: `", ++cnt );
                dump( *argv );
                printf( "'\n" );
        }
        sys$exit( 1 );
}
#endif /* EXAMPLE */

#endif /* VAX11C */