>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


Reply via email to