sbase

suckless unix tools
git clone git://git.2f30.org/sbase.git
Log | Files | Refs | README | LICENSE

commit 4bdf9a9658c3de471418c47f55c1d4571a945ab1
parent 71cbd12ede838f8193ea5d13e3692cc57c0cf942
Author: sin <sin@2f30.org>
Date:   Fri Jan  3 11:52:47 +0000

Add initial version of xargs(1)

Diffstat:
Makefile | 1+
xargs.1 | 28++++++++++++++++++++++++++++
xargs.c | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 270 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile @@ -93,6 +93,7 @@ SRC = \ stat.c \ wc.c \ who.c \ + xargs.c \ yes.c OBJ = $(SRC:.c=.o) $(LIB) diff --git a/xargs.1 b/xargs.1 @@ -0,0 +1,28 @@ +.TH XARGS 1 sbase\-VERSION +.SH NAME +xargs \- constuct argument list(s) and execute command +.SH SYNOPSIS +.B xargs +.RB [ \-r ] +.RI [ cmd +.RI [arg... ] ] +.SH DESCRIPTION +xargs reads space, tab, newline and EOF delimited strings from stdin +and executes the specified cmd with the strings as arguments. + +Any arguments specified on the command line are given to the command upon +each invocation, followed by some number of the arguments read from +stdin. The command is repeatedly executed one or more times until stdin +is exhausted. + +Spaces, tabs and newlines may be embedded in arguments using single (`'') +or double (`"') quotes or backslashes ('\\'). Single quotes escape all +non-single quote characters, excluding newlines, up to the matching single +quote. Double quotes escape all non-double quote characters, excluding +newlines, up to the matching double quote. Any single character, including +newlines, may be escaped by a backslash. +.SH OPTIONS +.TP +.BI \-r +Do not run the command if there are no arguments. Normally the command is +executed at least once even if there are no arguments. diff --git a/xargs.c b/xargs.c @@ -0,0 +1,241 @@ +/* See LICENSE file for copyright and license details. */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> +#include "text.h" +#include "util.h" + +enum { + NARGS = 5000 +}; + +static int inputc(void); +static void deinputc(int); +static void fillbuf(int); +static void eatspace(void); +static int parsequote(int); +static void parseescape(void); +static char *poparg(void); +static void pusharg(char *); +static int runcmd(void); + +static char **cmd; +static char *argb; +static size_t argbsz = 1; +static size_t argbpos; +static int rflag = 0; + +static void +usage(void) +{ + eprintf("usage: %s [-r] [cmd [arg...]]\n", argv0); +} + +int +main(int argc, char *argv[]) +{ + long argsz, argmaxsz; + char *arg; + int i; + + ARGBEGIN { + case 'r': + rflag = 1; + break; + default: + usage(); + } ARGEND; + + argmaxsz = sysconf(_SC_ARG_MAX); + if (argmaxsz < 0) + eprintf("sysconf:"); + /* Leave some room for environment variables */ + argmaxsz -= 4 * 1024; + + cmd = malloc(NARGS * sizeof(*cmd)); + if (!cmd) + eprintf("malloc:"); + + argb = malloc(argbsz); + if (!argb) + eprintf("malloc:"); + + do { + argsz = 0; i = 0; + if (argc > 0) { + for (; i < argc; i++) { + cmd[i] = strdup(argv[i]); + argsz += strlen(cmd[i]) + 1; + } + } else { + cmd[i] = strdup("/bin/echo"); + argsz += strlen(cmd[i]) + 1; + i++; + } + while ((arg = poparg())) { + if (argsz + strlen(arg) + 1 > argmaxsz || + i >= NARGS - 1) { + pusharg(arg); + break; + } + cmd[i] = strdup(arg); + argsz += strlen(cmd[i]) + 1; + i++; + } + cmd[i] = NULL; + if (i == 1 && rflag == 1) + ; + else + if (runcmd() == -1) + arg = NULL; + for (; i >= 0; i--) + free(cmd[i]); + } while (arg); + + free(argb); + free(cmd); + return 0; +} + +static int +inputc(void) +{ + int ch; + + ch = getc(stdin); + if (ch == EOF && ferror(stdin)) + eprintf("stdin: read error:"); + return ch; +} + +static void +deinputc(int ch) +{ + ungetc(ch, stdin); +} + +static void +fillbuf(int ch) +{ + if (argbpos >= argbsz) { + argbsz *= 2; + argb = realloc(argb, argbsz); + if (!argb) + eprintf("realloc:"); + } + argb[argbpos] = ch; +} + +static void +eatspace(void) +{ + int ch; + + while ((ch = inputc()) != EOF) { + switch (ch) { + case ' ': case '\t': case '\n': + break; + default: + deinputc(ch); + return; + } + } +} + +static int +parsequote(int q) +{ + int ch; + + while ((ch = inputc()) != EOF) { + if (ch == q) { + fillbuf('\0'); + return 0; + } + if (ch != '\n') { + fillbuf(ch); + argbpos++; + } + } + return -1; +} + +static void +parseescape(void) +{ + int ch; + + if ((ch = inputc()) != EOF) { + fillbuf(ch); + argbpos++; + } +} + +static char * +poparg(void) +{ + int ch; + + argbpos = 0; + eatspace(); + while ((ch = inputc()) != EOF) { + switch (ch) { + case ' ': case '\t': case '\n': + fillbuf('\0'); + deinputc(ch); + return argb; + case '\'': + if (parsequote('\'') == -1) + enprintf(EXIT_FAILURE, + "unterminated single quote\n"); + break; + case '\"': + if (parsequote('\"') == -1) + enprintf(EXIT_FAILURE, + "unterminated double quote\n"); + break; + case '\\': + parseescape(); + break; + default: + fillbuf(ch); + argbpos++; + break; + } + } + if (argbpos > 0) { + fillbuf('\0'); + return argb; + } + return NULL; +} + +static void +pusharg(char *arg) +{ + char *p; + + for (p = &arg[strlen(arg) - 1]; p >= arg; p--) + deinputc(*p); +} + +static int +runcmd(void) +{ + pid_t pid; + int status; + + pid = fork(); + if (pid < 0) + eprintf("fork:"); + if (pid == 0) { + execvp(*cmd, cmd); + eprintf("execvp %s:", *cmd); + } + wait(&status); + if (WIFEXITED(status) && WEXITSTATUS(status) == 255) + return -1; + return 0; +}