From 9a95b64618a1d3dfce4cb15874161636636de862 Mon Sep 17 00:00:00 2001 From: dtb Date: Tue, 19 Jul 2022 16:52:28 -0400 Subject: [PATCH] pscat(1) --- .gitignore | 1 + Makefile | 7 +++++ man/pscat.1 | 33 ++++++++++++++++++++ src/pscat.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 man/pscat.1 create mode 100644 src/pscat.c diff --git a/.gitignore b/.gitignore index eeb8b1a..6c1976f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ bin/id bin/lowercase bin/nonzero bin/nutshell +bin/pscat bin/rldecode bin/rlencode bin/roll diff --git a/Makefile b/Makefile index 54d49c0..91e6dff 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ cleanprograms: $(RM) bin/mm $(RM) bin/multiply $(RM) bin/nonzero + $(RM) bin/pscat $(RM) bin/simexec $(RM) bin/sleep $(RM) bin/streq @@ -90,6 +91,12 @@ nutshell.o: libio usefulmacros src/nutshell.c src/nutshell.h src/nutshell_builti nutshell: libio nutshell.o $(CC) $(CFLAGS) -o bin/nutshell build/libio.o build/nutshell.o +pscat.o: libio src/pscat.c + $(CC) $(CFLAGS) -c -o build/pscat.o src/pscat.c + +pscat: libio pscat.o + $(CC) $(CFLAGS) -o bin/pscat build/libio.o build/pscat.o + roll.o: lib/libio.h src/roll.c sysexits $(CC) $(CFLAGS) -c -o build/roll.o src/roll.c diff --git a/man/pscat.1 b/man/pscat.1 new file mode 100644 index 0000000..43ad775 --- /dev/null +++ b/man/pscat.1 @@ -0,0 +1,33 @@ +.TH PSCAT 1 + +.SH NAME + +pscat \- concatenate the output of processes + +.SH SYNOPSIS + +pscat +"[" +.RB [ utility +.RB [ argument... ]] +"]" ... + +.SH DESCRIPTION + +Pscat executes multiple commands, one after the other. +This allows multiple processes to be piped as one to another program. + +.SH DIAGNOSTICS + +Pscat will print an error message and exit with the appropriate status from sysexits(3) if executed improperly. +Pscat will exit with the sum of the child processes' exit statuses if run correctly. + +.SH BUGS + +Pscat's exit status isn't useful when run correctly; there's no way to tell which process failed if one did. +This issue of ergonomics isn't obviously mendable as processes' standard outputs and standard errors are meant to both be conveyed. +If either could be ignored the individual exit statuses could simply be printed. + +.SH COPYRIGHT + +Public domain. diff --git a/src/pscat.c b/src/pscat.c new file mode 100644 index 0000000..ad8c2bd --- /dev/null +++ b/src/pscat.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include "libio.h" + +static char *program_name = "pscat"; + +/* Originally designed to use parentheses but changed to brackets to escape the + * hassle of escaping them from the Bourne shell. */ +#define L_PAREN ASCII_LEFT_SQUARE_BRACKET +#define R_PAREN ASCII_RIGHT_SQUARE_BRACKET + +/* Test string containing { c, '\0' } without iteration. + * Theoretically saves a little bit of time compared to strcmp(3). */ +int +scmpflat(char *s, int c){ + return + s[0] != '\0' + && s[1] == '\0' + && s[0] == c + ; +} + +/* Verifies arguments to pscat are sensible. */ +int +check_arg(char **argv){ + enum { + UNINITIALIZED = 0, + INLPAREN = 1, + NORMAL = 2 + } s; + int terms; + + for(s = UNINITIALIZED, terms = 0; *argv != NULL; ++argv) + switch(s){ + case UNINITIALIZED: case NORMAL: + if(scmpflat(*argv, L_PAREN)) + s = INLPAREN; + else + return 0; /* syntax error */ + break; + case INLPAREN: + if(scmpflat(*argv, R_PAREN)) + { ++s; ++terms; } + break; + } + + return terms; +} + +int main(int argc, char *argv[]){ + char *argv0; + char **psstart; + int child; + int i; + int retval; + int terms; + + argv0 = argv[0] == NULL ? program_name : argv[0]; + retval = 0; + + if((terms = check_arg(++argv)) == 0){ + write(2, "Usage: ", 7); + fdprint(2, argv0); + write(2, " \"[\" [utility [argument...]] \"]\" ...\n", 37); + return EX_USAGE; + } + + /* loop starts with *argv -> the next L_PAREN */ + for(i = 0; i < terms; ++i){ + psstart = ++argv; + while(!scmpflat(*++argv, R_PAREN)); + /* *argv -> the corresponding R_PAREN. turn it into NULL to + * terminate the argument list to send to execvp(3) */ + *argv = NULL; + if(fork() == 0){ + execvp(psstart[0], psstart); + }else + wait(&child); + /* interpret status information from wait(2) */ + if(WIFEXITED(child)) + retval += WEXITSTATUS(child); + ++argv; + } + + return retval; +}