jdc@naucse.UUCP (John Campbell) (03/04/88)
Recently people have asked for, and one nice person posted, a way to expand wild-cards in 'C' programs. Wouldn't it be nice if we could write echo.c out of Kernighan and Ritchie and expect the following to work on VMS--by adding just one line of code? echo *.* echo hello >foo.out echo [-.work...]*.rn% >>foo.out The following two files allow VMS 'C' programmers a few less headaches when porting unix applications or when trying to build a unix-like environment. The first is simply echo.c with the extra line (actually 3 lines since I like to surround it with #ifdef VMS) that parses the passed in command line looking for redirection and wild-cards before executing the code. The second file (redexp.c) is designed to work as shown in echo.c--as an include file. Since you are including code--which is stylistically awful--I expect many people will change this scheme to be a link option. I hope the ideas are clear, however, and easy to improve upon. $ cc echo.c $ link echo,sys$library:vaxcrtl/lib $ echo = "$" + f$logical( "SYS$DISK" ) + f$directory() + "echo.exe" $ echo away we go *.c >foo.out Note that the redirect file does not support a space between the redirection symbol (<,>,>>) and the file name and that there are options to both redirect and expand that can be set by defining _N_FARGS or _E_FLAG. Much thanks to Gregg Townsend for, as always, leading off with a nice idea. ==================================ECHO.C===================== #include <stdio.h> /* echo.c essentially from page 111 of K&R... */ #ifdef VMS #include "redexp.vms" /* ...except we admit we are running VMS. */ #endif main(argc, argv) /* echo arguments; first version */ int argc; char *argv[]; { int i; for(i=1; i<argc; i++) printf("%s %c", argv[i], (i<argc-1) ? ' ':'\n'); } ====================================REDEXP.VMS================== /* This 'C' module may be included prior to the ``main'' programs on VMS in order to allow 'C' arguments to contain redirection symbols (<,>,>>) and VMS wild cards (*,%, ...], [-). By including this module, two programs redirect() and expand() are run prior to turning control over to your main() entry point. redirect-- Gregg Townsend circa 1983, expand-- John Campbell circa 1987 This code is public domain, others may use it freely. Credit, however, to Gregg Townsend (who wrote ``redirect()'') and John Campbell (who followed with ``expand()'') would be appreciated. If someone writes the next logical successor ``pipe()'', please email a copy to ...!arizona!naucse!jdc (John Campbell) (Gregg works on unix :-). */ #include <rms> /* No easy way to tell if this has already been included. */ #ifndef ERANGE /* Include only if missing. */ #include <stdlib> #endif #include <stdio.h> /* Stdio.h won't include itself twice. */ /* Expansion of wild cards is done using RMS. */ typedef struct NAMBLK { struct NAM nam; /* VMS nam block structure */ char es[NAM$C_MAXRSS], /* Extended string */ rs[NAM$C_MAXRSS]; /* Resultant string */ }; #define ErrorExit 1 /* Allow the user to override _N_FARGS or _E_FLAG if they wish. */ #ifndef _N_FARGS #define _N_FARGS 0 #endif #ifndef _E_FLAG #define _E_FLAG 0 #endif /* Since the following will possibly be included in a single module, try hard to avoid name conflicts. (Just being static doesn't cut it if compiled in the same module.) */ #define redirect _r_edirect #define filearg _f_ilearg #define expand _e_xpand #define wild_found _w_ild_found #define wild_expand _w_ild_expand main(argc, argv, envp) int argc; char *argv[], *envp[]; { char **expand(); redirect (&argc, argv, _N_FARGS); argv = expand (&argc, argv, _E_FLAG); /* Make the user's main entry point this routine's entry point. */ #define main _user_main _user_main (argc, argv, envp); } /* * redirect(&argc,argv,nfargs) - redirect standard I/O * int *argc number of command arguments (from call to main) * char *argv[] command argument list (from call to main) * int nfargs number of filename arguments to process * * argc and argv will be adjusted by redirect. * * redirect processes a program's command argument list and handles redirection * of stdin, and stdout. Any arguments which redirect I/O are removed from the * argument list, and argc is adjusted accordingly. redirect would typically be * called as the first statement in the main program. * * Files are redirected based on syntax or position of command arguments. * Arguments of the following forms always redirect a file: * * <file redirects standard input to read the given file * >file redirects standard output to write to the given file * >>file redirects standard output to append to the given file * * It is often useful to allow alternate input and output files as the * first two command arguments without requiring the <file and >file * syntax. If the nfargs argument to redirect is 2 or more then the * first two command arguments, if supplied, will be interpreted in this * manner: the first argument replaces stdin and the second stdout. * A filename of "-" may be specified to occupy a position without * performing any redirection. * * If nfargs is 1, only the first argument will be considered and will * replace standard input if given. Any arguments processed by setting * nfargs > 0 will be removed from the argument list, and again argc will * be adjusted. Positional redirection follows syntax-specified * redirection and therefore overrides it. * */ redirect(argc,argv,nfargs) int *argc, nfargs; char *argv[]; { int i; i = 1; while (i < *argc) { /* for every command argument... */ switch (argv[i][0]) { /* check first character */ case '<': /* <file redirects stdin */ filearg(argc,argv,i,1,stdin,"r"); break; case '>': /* >file or >>file redirects stdout */ if (argv[i][1] == '>') filearg(argc,argv,i,2,stdout,"a"); else filearg(argc,argv,i,1,stdout,"w"); break; default: /* not recognized, go on to next arg */ i++; } } if (nfargs >= 1 && *argc > 1) /* if positional redirection & 1 arg */ filearg(argc,argv,1,0,stdin,"r"); /* then redirect stdin */ if (nfargs >= 2 && *argc > 1) /* likewise for 2nd arg if wanted */ filearg(argc,argv,1,0,stdout,"w");/* redirect stdout */ } /* filearg(&argc,argv,n,i,fp,mode) - redirect and remove file argument * int *argc number of command arguments (from call to main) * char *argv[] command argument list (from call to main) * int n argv entry to use as file name and then delete * int i first character of file name to use (skip '<' etc.) * FILE *fp file pointer for file to reopen (typically stdin etc.) * char mode[] file access mode (see freopen spec) */ filearg(argc,argv,n,i,fp,mode) int *argc, n, i; char *argv[], mode[]; FILE *fp; { if (strcmp(argv[n]+i,"-")) /* alter file if arg not "-" */ fp = freopen(argv[n]+i,mode,fp); if (fp == NULL) { /* abort on error */ fprintf(stderr,"%%can't open %s",argv[n]+i); exit(ErrorExit); } for ( ; n < *argc; n++) /* move down following arguments */ argv[n] = argv[n+1]; *argc = *argc - 1; /* decrement argument count */ } /* EXPAND code. */ /* Global prototype. */ char **expand (int *argc, const char *argv[], const int flag); /*- ``expand()'' is a routine to expand wild-cards to file specifications. This routine is often used in conjunction with ``redirect()'' to provide both wild card expansion and standard file redirection prior to doing any real work in a 'C' program. Normal usage is to include the following line prior to using argc or argv in main(): argv = expand (&argc, argv, 0); ``argc'' will be adjusted by ``expand()'', the return value from expand will replace ``argv''. ``expand()'' processes a program's command argument list and expands any wild cards into zero or more argv entries. Only arguments that posses VMS wild-cards are expanded. Wild cards searched for are ``*'', ``%'', ``...]'', and ``[-''. If the wild-card is found inside a single or double quote ("*" or '%') then they are not counted as wild-cards. Be aware that the expansion of a VMS wild card will match all VMS files, including directory files (".DIR;1"). NOTE: The use of quotes in VMS requires thinking about how the CLI expands things before handing the argument line over to your program. Do not expect "*" to avoid expansion, use """*""" instead. Likewise, expression substitution precludes the use of (') to quote wild cards: $ A := HELLO $ ECHO 'a' ! 'C' program that calls ``expand()'' hello The easiest way to escape a wild-card may be "'*'". The point is that ``expand()'' will only recognize quotes passed into main(). ``expand()'' references the VMS runtime routines, you will need to link with the 'C' RTL whenever expand is used. Parameters: argc: Pointer to the number of command arguments (from main), the contents of this parameter are modified. argv: Pointer to the initial command argument list (from main), the contents are copied into a new array which is returned from this routine. flag: Flag indicating how to expand wild-cards: 0 - Complete file name expansion 1 - only file name (no directory or version). 2 - directory info and file name (no version). 3 - file name and version info (no directory). -*/ /* Local prototypes. */ int wild_found (char *string); char **wild_expand (const char *string, char **argv, int *argc, int extra, int flag); /* General note: removing the prototyping and const keywords should allow this code to compile with VMS 'C' compilers prior to version 2.3-024. */ char **expand (int *argc, char *argv[], int flag) /* Routine to expand all the arguments from main(argc,argv). The return value is a pointer to a new (expanded) argv array. Parameters: argc: Pointer to the number of command arguments (from main), the contents of this parameter are modified. argv: Pointer to the initial command argument list (from main), the contents are copied into a new array which is returned from this routine. flag: Flag indicating how to expand wild-card: 0 - Complete file name expansion 1 - only file name (no directory or version). 2 - directory info and file name (no version). 3 - file name and version info (no directory). */ { int i, nargc; char **nargv, **wild_expand(); /* Get an initial amount of memory for the master nargv array. */ if ((nargv = (char **)malloc ((*argc+1) * sizeof (char *))) == NULL) { fprintf (stderr, "Not enough memory to expand argument list\n"); exit (ErrorExit); } /* Copy the command string (0th argument). */ nargv[0] = argv[0]; /* Copy each argument, expanding those that have wild characters. */ for (nargc = i = 1; i < *argc; i++) { if (wild_found(argv[i])) nargv = wild_expand(argv[i], nargv, &nargc, *argc-i, flag); else nargv[nargc++] = argv[i]; } *argc = nargc; nargv[nargc] = NULL; /* realloc always 0 fills, but... */ return nargv; } static int wild_found (char *string) /* Routine to search the given string for a VMS wild-card pattern. Returns 1 if "*", "%", "[-", or "...]" is found. (This may not be all VMS wild-cards but it is enough for now--anyone that wants to recognize others can change this code.) Parameter: string: '\0' terminated character array. */ { int state = 0; /* State of 0 is "rest" state. State 1 on our way to [-, states 2-4 on our way to ...], negative states indicate the two quotes (' -10, " -1). */ for ( ;*string; string++) { switch (*string) { case '*': case '%': if (state >= 0) return 1; /* Unquoted % or * found. */ break; case '[': if (state >= 0) state = 1; break; case ']': if (state == 4) return 1; /* Unquoted ...] found. */ else if (state >= 0) state = 0; break; case '-': if (state == 1) return 1; /* Unquoted [- found. */ else if (state >= 0) state = 0; break; case '.': if (state == 1 || state == 0) state = 2; /* First '.' */ else if (state > 1 && state < 5) state++; /* ... == states 2, 3, 4 */ else if (state >= 0) state = 0; break; case '\'': if (state <= -10) state += 10; /* One ', possibly one " also */ else if (state < 0) state -= 10; /* 0 ', possibly one " */ else state = -10; /* No ' or " prior to this ' */ break; case '"': if (state == -11) state = -10; /* Both " and ' prior to this. */ else if (state == -10) state = -11; /* A ' prior to this. */ else if (state == -1) state = 0; /* A " prior to this. */ else state = -1; /* No ' or " prior to this " */ break; } } return 0; } static char **wild_expand (const char *wild, char **argv, int *argc, int extra, int flag) /* Routine to expand wild into new arguments appended to the end of argv[*argc]. This routine must realloc in order to make room for the individual arguments and malloc for enough space for each of the arguments. The return value is a new **argv. Parameters: wild: '\0' terminated string that needs to be expanded. argv: initial starting address of the argv array. argc: pointer to an integer that tells the current end of the argument list. extra: The number of extra pointers that the returned argv must have available for future assignments. flag: Flag indicating how to expand wild-card: 0 - Complete file name expansion 1 - only file name (no directory or version). 2 - directory info and file name (no version) 3 - file name and version info (no directory). */ { int more_to_go = 1, err, length, status, len_wild; char *namptr; struct FAB fab_blk; struct NAMBLK nam_blk; len_wild = strlen(wild); /* Initialize all the fab and nam fields needed for parse and search */ fab_blk = cc$rms_fab; /* Initialize FAB structure */ nam_blk.nam = cc$rms_nam; /* Initialize NAM structure */ fab_blk.fab$l_dna = ".*"; /* Default file specif. */ fab_blk.fab$b_dns = 2; /* Length of default spec. */ fab_blk.fab$l_nam = &nam_blk.nam; /* Set address of NAM in FAB*/ nam_blk.nam.nam$b_ess = NAM$C_MAXRSS; /* Set extended string size*/ nam_blk.nam.nam$l_esa = &nam_blk.es; /* and address */ nam_blk.nam.nam$b_rss = NAM$C_MAXRSS; /* Set resultant string size*/ nam_blk.nam.nam$l_rsa = &nam_blk.rs; /* and address */ nam_blk.nam.nam$l_rlf = NULL; /* No related file address */ fab_blk.fab$l_fna = wild; /* Address of file name string */ fab_blk.fab$b_fns = len_wild; /* Length of file name string */ /* Prepare to enter the search loop, parse fab. */ err = SYS$PARSE (&fab_blk); /* Catch the directory not found error and return no files found. */ if (err != RMS$_NORMAL) exit(err); while (more_to_go) { err = SYS$SEARCH (&fab_blk); if (err == RMS$_NMF || err == RMS$_FNF) more_to_go = 0; /* Done, no more files found */ else if (err != RMS$_NORMAL) exit (err); else { /* Count that we now have this many arguments. */ (*argc)++; /* Make sure there is room for a new pointer. */ if ((argv = realloc (argv, (*argc + extra)*sizeof(char *))) == NULL) { fprintf (stderr, "Not enough memory to expand argument list\n"); exit(ErrorExit); } /* Move the right name into the list. */ switch (flag) { case 0: /* Complete file name */ length = nam_blk.nam.nam$b_rsl; namptr = &nam_blk.rs; break; case 1: /* File name only (no directory or version). */ length = nam_blk.nam.nam$b_name + nam_blk.nam.nam$b_type; namptr = nam_blk.nam.nam$l_name; break; case 2: /* directory and file name (no version) */ length = nam_blk.nam.nam$b_rsl - nam_blk.nam.nam$b_ver; namptr = &nam_blk.rs; break; case 3: /* File name and version (no directory). */ length = nam_blk.nam.nam$b_name + nam_blk.nam.nam$b_type + nam_blk.nam.nam$b_ver; namptr = nam_blk.nam.nam$l_name; break; default: fprintf (stderr, "illegal flag used in VMS expand call\n"); exit (ErrorExit); } /* Copy the requested string into the argument array. */ if ((argv[*argc-1] = malloc (length+1)) == NULL) { fprintf (stderr, "Not enough memory to expand argument list\n"); exit (ErrorExit); } strncpy (argv[*argc-1], namptr, length); argv[*argc-1][length] = '\0'; } } return (argv); } /* Remove all the defines that might affect the user's code. */ #undef redirect #undef filearg #undef expand #undef wild_found #undef wild_expand #ifdef ECHO /* Example code using expand(). */ # ifndef __FILE #include stdio #endif main(argc, argv) int argc; char *argv[]; /* This main program allows you to run experiments with ``expand()''. Try $ echo *.*, $ echo -f1 [-...]*.*, $ echo -f[0-3] *.*. Questions about using "%", "\", etc. may be answered by testing with this version of echo. To use this, of course, you need to link directly with expand-- avoiding the substitution of main with _user_main above. */ { int i, flag=0; char **expand(); for(i=1; i<argc; i++) printf("%s %c", argv[i], (i<argc-1) ? ' ':'\n'); if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'f') flag = atoi (&argv[1][2]); argv = expand (&argc, argv, flag); printf ("\n\n"); for(i=1; i<argc; i++) printf("%s %c", argv[i], flag%2 == 0 ? '\n' : i%4 == 0 ? '\n':'\t'); } #endif /* ECHO */ -- John Campbell ...!arizona!naucse!jdc unix? Sure send me a dozen, all different colors.