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.
This commit is contained in:
Hiltjo Posthuma 2025-07-31 15:30:39 +02:00 committed by Roberto E. Vargas Caballero
parent 7c3ccc8659
commit 635515f6e3
3 changed files with 116 additions and 36 deletions

5
TODO
View File

@ -85,3 +85,8 @@ tr
sbase-box
---------
* List of commands does not contain `install` (only `xinstall`).
xargs
-----
* Add -L.

45
xargs.1
View File

@ -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

102
xargs.c
View File

@ -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;