sbase

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

commit 635515f6e3b3d7f311a3ebc04dd94689af1a77a0
parent 7c3ccc8659099199f1b3c24e0c534c1795c88a13
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date:   Thu, 31 Jul 2025 15:30:39 +0200

xargs.c: implement -p, -P and -0, add TODO for -L

From 2350f520a6dd7e293c5505aaa0983853cdd41ee6 Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma <hiltjo@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.

Diffstat:
MTODO | 5+++++
Mxargs.1 | 45++++++++++++++++++++++++++++++++-------------
Mxargs.c | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 116 insertions(+), 36 deletions(-)

diff --git 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 @@ -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 @@ -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; } @@ -129,6 +127,10 @@ poparg(void) if (parseescape() < 0) eprintf("backslash at EOF\n"); break; + case '\0': + /* NUL separator: no escaping */ + if (nulflag) + goto out; default: fill: fillargbuf(ch); @@ -143,22 +145,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 +203,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 +231,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 +257,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 +289,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 +349,8 @@ main(int argc, char *argv[]) free(argb); + waitchld(1); + if (nerrors || (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))) ret = 123;