>From 2350f520a6dd7e293c5505aaa0983853cdd41ee6 Mon Sep 17 00:00:00 2001 From: Hiltjo Posthuma <hil...@codemadness.org> Date: Thu, 31 Jul 2025 14:40:43 +0200 Subject: [PATCH 3/4] xargs.c: implement -p, -P and -0, add TODO for -L
- Add option to read arguments separated by NUL (-0). Useful with find -print0 for example. - Add very useful parallel option (-P). POSIX vaguely mentions parallel operations, but this is commonly supported and very useful. For example OpenBSD xargs supports it since at least 2003. GNU xargs since at least 1996. - Add prompt option (-p), (POSIX). - Add a TODO for xargs -L (POSIX extension). - Documentation and man page improvements. --- TODO | 5 +++ xargs.1 | 45 +++++++++++++++++------- xargs.c | 106 ++++++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 120 insertions(+), 36 deletions(-) diff --git a/TODO b/TODO index 575b3d9..38c9f86 100644 --- a/TODO +++ b/TODO @@ -85,3 +85,8 @@ tr sbase-box --------- * List of commands does not contain `install` (only `xinstall`). + + +xargs +----- +* Add -L. diff --git a/xargs.1 b/xargs.1 index 3023e59..9fa9f10 100644 --- a/xargs.1 +++ b/xargs.1 @@ -1,4 +1,4 @@ -.Dd July 30, 2023 +.Dd July 30, 2025 .Dt XARGS 1 .Os sbase .Sh NAME @@ -6,10 +6,11 @@ .Nd construct argument lists and execute command .Sh SYNOPSIS .Nm -.Op Fl rtx +.Op Fl 0prtx .Op Fl E Ar eofstr .Op Fl I Ar replstr .Op Fl n Ar num +.Op Fl P Ar maxprocs .Op Fl s Ar num .Op Ar cmd Op Ar arg ... .Sh DESCRIPTION @@ -26,7 +27,7 @@ stdin. The command is repeatedly executed one or more times until stdin is exhausted. .Pp Spaces, tabs and newlines may be embedded in arguments using single (`'') -or double (`"') quotes or backslashes ('\\'). +or double (`"') quotes or backslashes ('\e'). 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 @@ -34,13 +35,12 @@ to the matching double quote. Any single character, including newlines, may be escaped by a backslash. .Sh OPTIONS .Bl -tag -width Ds -.It Fl n Ar num -Use at most -.Ar num -arguments per command line. -.It Fl 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. +.It Fl 0 +Change +.Nm +to expect NUL ('\e0') characters as separators, instead of spaces +and newlines. +The quoting mechanisms described above are not performed. .It Fl E Ar eofstr Use .Ar eofstr @@ -51,11 +51,32 @@ Use as the placeholder for the argument. Sets the arguments count to 1 per command line. It also implies the option x. +.It Fl n Ar num +Use at most +.Ar num +arguments per command line. +.It Fl p +Prompt mode: the user is asked whether to execute +.Ar cmd +at each invocation. +Trace mode (-t) is turned on to write the command instance to be executed, +followed by a prompt to standard error. +An affirmative response read from +.Pa /dev/tty +executes the command, otherwise it is skipped. +.It Fl P Ar maxprocs +Parallel mode: run at most maxprocs invocations of +.Ar cmd +at once. +.It Fl 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. .It Fl s Ar num Use at most .Ar num bytes per command line. .It Fl t +Enable trace mode. Write the command line to stderr before executing it. .It Fl x Terminate if the command line exceeds the system limit or the number of bytes @@ -95,9 +116,7 @@ The .Nm utility is compliant with the .St -p1003.1-2013 -specification except from the -.Op Fl p -flag. +specification. .Pp The .Op Fl r diff --git a/xargs.c b/xargs.c index 3035bda..0022180 100644 --- a/xargs.c +++ b/xargs.c @@ -19,14 +19,15 @@ static int eatspace(void); static int parsequote(int); static int parseescape(void); static char *poparg(void); -static void waitchld(void); +static void waitchld(int); static void spawn(void); static size_t argbsz; static size_t argbpos; -static size_t maxargs = 0; -static int nerrors = 0; -static int rflag = 0, nflag = 0, tflag = 0, xflag = 0, Iflag = 0; +static size_t maxargs; +static size_t curprocs, maxprocs = 1; +static int nerrors; +static int nulflag, nflag, pflag, rflag, tflag, xflag, Iflag; static char *argb; static char *cmd[NARGS]; static char *eofstr; @@ -59,10 +60,7 @@ eatspace(void) int ch; while ((ch = inputc()) != EOF) { - switch (ch) { - case ' ': case '\t': case '\n': - break; - default: + if (nulflag || !(ch == ' ' || ch == '\t' || ch == '\n')) { ungetc(ch, stdin); return ch; } @@ -110,6 +108,14 @@ poparg(void) if (eatspace() < 0) return NULL; while ((ch = inputc()) != EOF) { + /* NUL separator: no escaping */ + if (nulflag) { + switch (ch) { + case '\0': goto out; + default: goto fill; + } + continue; + } switch (ch) { case ' ': case '\t': @@ -143,22 +149,55 @@ out: } static void -waitchld(void) +waitchld(int waitall) { + pid_t pid; int status; - wait(&status); - if (WIFEXITED(status)) { - if (WEXITSTATUS(status) == 255) - exit(124); - if (WEXITSTATUS(status) == 127 || - WEXITSTATUS(status) == 126) - exit(WEXITSTATUS(status)); - if (status) - nerrors++; + while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ? + WNOHANG : 0)) > 0) { + curprocs--; + + if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 255) + exit(124); + if (WEXITSTATUS(status) == 127 || + WEXITSTATUS(status) == 126) + exit(WEXITSTATUS(status)); + if (WEXITSTATUS(status)) + nerrors++; + } + if (WIFSIGNALED(status)) + exit(125); } - if (WIFSIGNALED(status)) - exit(125); + if (pid == -1 && errno != ECHILD) + eprintf("waitpid:"); +} + +static int +prompt(void) +{ + FILE *fp; + int ch, ret; + + if (!(fp = fopen("/dev/tty", "r"))) + return -1; + + fputs("?...", stderr); + fflush(stderr); + + ch = fgetc(fp); + ret = (ch == 'y' || ch == 'Y'); + if (ch != EOF && ch != '\n') { + while ((ch = fgetc(fp)) != EOF) { + if (ch == '\n') + break; + } + } + + fclose(fp); + + return ret; } static void @@ -168,16 +207,25 @@ spawn(void) int first = 1; char **p; - if (tflag) { + if (pflag || tflag) { for (p = cmd; *p; p++) { if (!first) fputc(' ', stderr); fputs(*p, stderr); first = 0; } + if (pflag) { + switch (prompt()) { + case -1: break; /* error */ + case 0: return; /* no */ + case 1: goto dospawn; /* yes */ + } + } fputc('\n', stderr); + fflush(stderr); } +dospawn: switch (fork()) { case -1: eprintf("fork:"); @@ -187,13 +235,14 @@ spawn(void) weprintf("execvp %s:", *cmd); _exit(126 + (savederrno == ENOENT)); } - waitchld(); + curprocs++; + waitchld(0); } static void usage(void) { - eprintf("usage: %s [-rtx] [-E eofstr] [-n num] [-s num] " + eprintf("usage: %s [-0prtx] [-E eofstr] [-n num] [-P maxprocs] [-s num] " "[cmd [arg ...]]\n", argv0); } @@ -212,10 +261,16 @@ main(int argc, char *argv[]) argmaxsz -= 4096; ARGBEGIN { + case '0': + nulflag = 1; + break; case 'n': nflag = 1; maxargs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); break; + case 'p': + pflag = 1; + break; case 'r': rflag = 1; break; @@ -238,6 +293,9 @@ main(int argc, char *argv[]) maxargs = 1; replstr = EARGF(usage()); break; + case 'P': + maxprocs = estrtonum(EARGF(usage()), 1, MIN(SIZE_MAX, LLONG_MAX)); + break; default: usage(); } ARGEND @@ -295,6 +353,8 @@ main(int argc, char *argv[]) free(argb); + waitchld(1); + if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))) ret = 123; -- 2.50.1