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:
M | TODO | | | 5 | +++++ |
M | xargs.1 | | | 45 | ++++++++++++++++++++++++++++++++------------- |
M | xargs.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;