[PATCH] Add new xargs -N (--newline) option for newline-terminated input

"Deven T. Corzine" <[email protected]>
Newsgroups gmane.comp.gnu.findutils.patches,gmane.comp.gnu.findutils.bugs
Message-ID <CAFVdu0QT9jD+QuZ9=DLDL3MfBNUqT08-GAXbmVrEXUzp3tQfmQ@mail.gmail.com>
Sorry, I didn't think to add [PATCH] to the subject line...

Deven

On Sun, Mar 2, 2025 at 5:05 AM Deven T. Corzine <[email protected]> wrote:

> The attached patch adds a new "-N" (or "--newline") option to xargs.  This
> is simply an alias for "-d'\n'" for convenience, since newline-terminated
> lists of filenames are very common, not just from "find -print" but also
> from many other tools that don't offer null-terminated options like "find
> -print0".  This option would be much more convenient and easier to
> discover/remember than the equivalent option "-d'\n'".
>
> This patch contains only 15 lines of code changes to xargs.c and similarly
> small changes for documentation and a test case, so I don't know if a
> copyright assignment is required here or not.  Let me know.
>
> Deven
>
> P.S. The second patch attached to this email just changes several of the
> "find ... | xargs" examples in the existing documentation to use "find ...
> -print0", in order to model best practices.  This change is independent of
> the -N option, so it's in a separate patch file.
>
0001-xargs-Add-N-or-newline-option-for-newline-terminated.patch (application/octet-stream, 12.3 KB)
From 629fe49d2d0a5a4ef949eb987920fe7b3f379ba9 Mon Sep 17 00:00:00 2001
From: "Deven T. Corzine" <[email protected]>
Date: Sun, 2 Mar 2025 01:43:08 -0500
Subject: [PATCH 1/2] xargs: Add -N (or --newline) option for
 newline-terminated input.

This new -N (or --newline) option is a convenience alias for -d'\n' to
process newline-terminated input, such as the output from "find -print".
Using "find -print0" is still better, but this option can also be useful
for working with other tools which don't offer null-terminated output.

* xargs/xargs.c: Add -N (or --newline) option for newline-terminated input.
* xargs/testsuite/xargs.gnu/newline.exp: Test case for -N option.
* xargs/testsuite/xargs.gnu/newline.xo: Expected output of test case.
* xargs/xargs.1: Document -N (--newline) option in man page.
* doc/find.texi: Document -N (--newline) option in texinfo documentation.
* NEWS: Add description of -N (--newline) option.
---
 NEWS                                  |  8 ++++++
 doc/find.texi                         | 40 ++++++++++++++++++++-------
 xargs/testsuite/xargs.gnu/newline.exp |  1 +
 xargs/testsuite/xargs.gnu/newline.xo  |  5 ++++
 xargs/xargs.1                         | 38 +++++++++++++++++++------
 xargs/xargs.c                         | 15 ++++++++--
 6 files changed, 86 insertions(+), 21 deletions(-)
 create mode 100644 xargs/testsuite/xargs.gnu/newline.exp
 create mode 100644 xargs/testsuite/xargs.gnu/newline.xo

diff --git a/NEWS b/NEWS
index 5b32c9fc..f03bd09d 100644
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,14 @@ GNU findutils NEWS - User visible changes.      -*- outline -*- (allout)
   argument of the command to be run.  While POSIX allows this for -exec, this is
   deemed insecure as an attacker could influence which files could be found.
 
+** New features in xargs
+
+  Added a new convenience option -N (--newline) as an alias for -d'\n' for
+  newline-terminated input.  This can be used with "find -print" output for
+  path names containing spaces (but not newlines), although "find -print0"
+  remains preferable.  This option can also be useful in conjunction with
+  other tools which don't offer null-terminated output like "find -print0".
+
 ** Documentation Changes
 
   The forthcoming Issue 8 of the POSIX standard will standardise "find
diff --git a/doc/find.texi b/doc/find.texi
index d441e01c..5e835736 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -1426,9 +1426,16 @@ files that contain the string, rather than the lines that contain it.
 The string argument (@samp{thing}) is actually a regular expression,
 so it can contain metacharacters.  This method can be refined a little
 by using the @samp{-r} option to make @code{xargs} not run @code{grep}
-if @code{find} produces no output, and using the @code{find} action
-@samp{-print0} and the @code{xargs} option @samp{-0} to avoid
-misinterpreting files whose names contain spaces:
+if @code{find} produces no output, and using the @code{xargs} option
+@samp{-N} or the @code{find} action @samp{-print0} with the
+@code{xargs} option @samp{-0} to avoid misinterpreting filenames
+containing spaces:
+
+@example
+find . -name '*.[ch]' -print | xargs -r -N grep -l thing
+@end example
+
+or better yet:
 
 @example
 find . -name '*.[ch]' -print0 | xargs -r -0 grep -l thing
@@ -2508,7 +2515,9 @@ and even newlines, it is not safe to process them using @code{xargs}
 in its default mode of operation.  But since most files' names do not
 contain blanks, this problem occurs only infrequently.  If you are
 only searching through files that you know have safe names, then you
-need not be concerned about it.
+need not be concerned about it.  (The @samp{-N} or @samp{--newline}
+option of @code{xargs} may be useful for filenames which contain
+spaces but not newlines.)
 
 Error messages issued by @code{find} and @code{locate} quote unusual
 characters in file names in order to prevent unwanted changes in the
@@ -3911,22 +3920,22 @@ rejecting this as unrecognized option.
 
 @item --arg-file@r{=@var{inputfile}}
 @itemx -a @r{@var{inputfile}}
-Read names from the file @var{inputfile} instead of standard input.
+Read items from the file @var{inputfile} instead of standard input.
 If you use this option, the standard input stream remains unchanged
 when commands are run. Otherwise, stdin is redirected from
 @file{/dev/null}.
 
 @item --null
 @itemx -0
-Input file names are terminated by a null character instead of by
-whitespace, and any quotes and backslash characters are not considered
-special (every character is taken literally).
-Disables the end-of-file string, which is treated like any other argument.
+Input items are terminated by a null character instead of by whitespace,
+and any quotes and backslash characters are not considered special (every
+character is taken literally).  Disables the end-of-file string, which is
+treated like any other argument.
 
 @item --delimiter @var{delim}
 @itemx -d @var{delim}
 
-Input file names are terminated by the specified character @var{delim}
+Input items are terminated by the specified character @var{delim}
 instead of by whitespace, and any quotes and backslash characters are
 not considered special (every character is taken literally).  Disables
 the logical end-of-file marker string, which is treated like any other
@@ -3982,6 +3991,17 @@ of counting the lines.  Implies @samp{-x}.  The @samp{-l} form of this
 option is deprecated in favour of the POSIX-compliant @samp{-L}
 option.
 
+@item --newline
+@itemx -N
+Input items are terminated by newlines, not by whitespace.  Spaces, tabs,
+quotes and backslash are not special (every character is taken literally).
+Disables the end-of-file string, which is treated like any other argument.
+Useful when the input consists of simply newline-separated items (such as
+file names from the find @samp{-print} option which might contain spaces
+or tabs), although it is almost always better to design your program to
+use @samp{--null} where this is possible.  (This option is an alias for
+@samp{-d'\\n'}.)
+
 @item --max-args=@var{max-args}
 @itemx -n @var{max-args}
 Use at most @var{max-args} arguments per command line.  Fewer than
diff --git a/xargs/testsuite/xargs.gnu/newline.exp b/xargs/testsuite/xargs.gnu/newline.exp
new file mode 100644
index 00000000..29318302
--- /dev/null
+++ b/xargs/testsuite/xargs.gnu/newline.exp
@@ -0,0 +1 @@
+xargs_start p {-N -n1} quotes.xi
diff --git a/xargs/testsuite/xargs.gnu/newline.xo b/xargs/testsuite/xargs.gnu/newline.xo
new file mode 100644
index 00000000..031e84fb
--- /dev/null
+++ b/xargs/testsuite/xargs.gnu/newline.xo
@@ -0,0 +1,5 @@
+   this is
+"quoted 	stuff"  
+and \
+an embedded   newline
+with 'single	quotes' as well.
diff --git a/xargs/xargs.1 b/xargs/xargs.1
index d238cfc0..b6fdd1dc 100644
--- a/xargs/xargs.1
+++ b/xargs/xargs.1
@@ -41,6 +41,7 @@ This will normally have significant performance benefits.
 Some commands can usefully be executed in parallel too; see the
 .B \-P
 option.
+
 .P
 Because Unix filenames can contain blanks and newlines, this default
 behaviour is often problematic; filenames containing blanks
@@ -58,7 +59,12 @@ If that program is GNU
 .B find
 for example, the
 .B \-print0
-option does this for you.
+option does this for you.  (The
+.B \-N
+/
+.B \-\-newline
+option may also be useful if filenames contain spaces but not newlines.)
+
 .P
 If any invocation of the command exits with a status of 255,
 .B xargs
@@ -72,7 +78,7 @@ Input items are terminated by a null character instead of by
 whitespace, and the quotes and backslash are not special (every
 character is taken literally).
 Disables the end-of-file string, which is treated like any other argument.
-Useful when input items might contain white space, quote marks, or backslashes.
+Useful when input items might contain whitespace, quote marks, or backslashes.
 The GNU find (and from Issue 8, POSIX) \-print0 option produces input suitable for this mode.
 
 .TP
@@ -100,10 +106,13 @@ every character in the input is taken literally.
 The
 .B \-d
 option disables any end-of-file string, which is treated like any
-other argument.
-You can use this option when the input consists of
-simply newline-separated items, although it is almost always better to
-design your program to use
+other argument.  You can use this option (or
+.B \-N
+/
+.B \-\-newline
+) when the input consists of simply newline-separated items (such as
+file names from the find \-print option which might contain spaces or tabs),
+although it is almost always better to design your program to use
 .B \-\-null
 where this is possible.
 
@@ -184,7 +193,20 @@ The
 option is deprecated since the POSIX standard specifies
 .B \-L
 instead.
+
 .TP
+.B \-N, \-\-newline
+Input items are terminated by newlines, not by whitespace.  Spaces, tabs,
+quotes and backslash are not special (every character is taken literally).
+Disables the end-of-file string, which is treated like any other argument.
+Useful when the input consists of simply newline-separated items (such as
+file names from the find \-print option which might contain spaces or tabs),
+although it is almost always better to design your program to use
+.B \-\-null
+where this is possible.  (This option is an alias for
+.B \-d'\\n'
+.)
+
 .BI \-n " max-args\fR, \fI" "\-\-max\-args" \fR=\fImax-args
 Use at most \fImax-args\fR arguments per command line.
 Fewer than
@@ -378,7 +400,7 @@ because it would not actually conflict.
 .
 .SH "EXAMPLES"
 .nf
-.B find /tmp \-name core \-type f \-print | xargs /bin/rm \-f
+.B find /tmp \-name core \-type f \-print | xargs \-N /bin/rm \-f
 
 .fi
 Find files named
@@ -387,7 +409,7 @@ in or below the directory
 .B /tmp
 and delete them.
 Note that this will work incorrectly if there are
-any filenames containing newlines or spaces.
+any file or directory names containing newlines.
 .P
 .B find /tmp \-name core \-type f \-print0 | xargs \-0 /bin/rm \-f
 
diff --git a/xargs/xargs.c b/xargs/xargs.c
index 517f963c..51a2eefd 100644
--- a/xargs/xargs.c
+++ b/xargs/xargs.c
@@ -154,7 +154,7 @@ static bool print_command = false; /* Option -t */
 static bool query_before_executing = false;
 
 /* The delimiter for input arguments.   This is only consulted if the
- * -0 or -d option had been given.
+ * -0, -d or -N option had been given.
  */
 static char input_delimiter = '\0';
 
@@ -179,6 +179,7 @@ static struct option const longopts[] =
   {"eof", optional_argument, NULL, 'e'},
   {"replace", optional_argument, NULL, 'I'},
   {"max-lines", optional_argument, NULL, 'l'},
+  {"newline", no_argument, NULL, 'N'},
   {"max-args", required_argument, NULL, 'n'},
   {"open-tty", no_argument, NULL, 'o'},
   {"interactive", no_argument, NULL, 'p'},
@@ -518,7 +519,7 @@ main (int argc, char **argv)
       bc_use_sensible_arg_max (&bc_ctl);
     }
 
-  while ((optc = getopt_long (argc, argv, "+0a:E:e::i::I:l::L:n:oprs:txP:d:",
+  while ((optc = getopt_long (argc, argv, "+0a:E:e::i::I:l::L:Nn:oprs:txP:d:",
                               longopts, &option_index)) != -1)
     {
       switch (optc)
@@ -596,6 +597,11 @@ main (int argc, char **argv)
             }
           break;
 
+        case 'N':
+          read_args = read_string;
+          input_delimiter = '\n';
+          break;
+
         case 'n':
           bc_ctl.args_per_exec = parse_num (optarg, 'n', 1L, -1L, 1);
           /* -n excludes -i -l.  */
@@ -714,7 +720,7 @@ main (int argc, char **argv)
   if (eof_str && (read_args == read_string))
     {
       error (0, 0,
-             _("warning: the -E option has no effect if -0 or -d is used.\n"));
+             _("warning: the -E option has no effect if -0, -d or -N is used.\n"));
     }
 
   /* If we had deferred failing due to problems in bc_init_controlinfo (),
@@ -1758,6 +1764,9 @@ usage (int status)
          "                                 command line\n"));
   HTL (_("  -l[MAX-LINES]                similar to -L but defaults to at most one non-\n"
          "                                 blank input line if MAX-LINES is not specified\n"));
+  HTL (_("  -N, --newline                items in input stream are separated by newlines,\n"
+         "                                 not by whitespace; disables quote, backslash\n"
+         "                                 and logical EOF processing (alias for -d'\\n')\n"));
   HTL (_("  -n, --max-args=MAX-ARGS      use at most MAX-ARGS arguments per command line\n"));
   HTL (_("  -o, --open-tty               Reopen stdin as /dev/tty in the child process\n"
          "                                 before executing the command; useful to run an\n"
-- 
2.34.1
0002-doc-Update-texinfo-documentation-to-use-print0-in-mo.patch (application/octet-stream, 2.9 KB)
From 1ff77f1a97dd61845a39dc6354f5b89748ef2915 Mon Sep 17 00:00:00 2001
From: "Deven T. Corzine" <[email protected]>
Date: Sun, 2 Mar 2025 03:54:50 -0500
Subject: [PATCH 2/2] doc: Update texinfo documentation to use -print0 in more
 examples.

Model best practices by using -print0 in more "find ... | xargs" examples.

* doc/find.texi: Use -print0 in more examples.
---
 doc/find.texi | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/doc/find.texi b/doc/find.texi
index 5e835736..a27a3cad 100644
--- a/doc/find.texi
+++ b/doc/find.texi
@@ -2540,7 +2540,7 @@ daily from @code{cron}, then any user can remove any file on the
 system:
 
 @example
-find / -name '#*' -atime +7 -print | xargs rm
+find / -name '#*' -atime +7 -print0 | xargs -0 rm
 @end example
 
 For example, you could do something like this:
@@ -2567,7 +2567,7 @@ eg$ mkdir u u/joeuser u/joeuser/.plan'
 eg$ echo > u/joeuser/.plan'
 /#foo'
 eg$ cd ..
-eg$ find . -name '#*' -print | xargs echo
+eg$ find . -name '#*' -print0 | xargs -0 echo
 ./# ./# /u/joeuser/.plan /#foo
 @end example
 
@@ -2781,7 +2781,7 @@ enough work that it takes significant time to run, even on a single
 image.  You could run:
 
 @example
-find originals -name '*.jpg' | xargs -L 1 makeallsizes
+find originals -name '*.jpg' -print0 | xargs -0 -L 1 makeallsizes
 @end example
 
 This will run @code{makeallsizes @var{filename}} once for each @code{.jpg}
@@ -2790,7 +2790,7 @@ two central processors, this script will only keep one of them busy.
 Instead, you could probably finish in about half the time by running:
 
 @example
-find originals -name '*.jpg' | xargs -L 1 -P 2 makeallsizes
+find originals -name '*.jpg' -print0 | xargs -0 -L 1 -P 2 makeallsizes
 @end example
 
 @code{xargs} will run the first two commands in parallel, and then
@@ -3050,7 +3050,7 @@ running @code{file} on files that are not regular files or are not
 executable.
 
 @example
-find /usr/local -type f -perm /a=x | xargs file |
+find /usr/local -type f -perm /a=x -print0 | xargs -0 file |
   grep 'not stripped' | cut -d: -f1
 @end example
 
@@ -4571,7 +4571,7 @@ directories lacking either user or group write permission (or both),
 and fix their permissions:
 
 @example
-find . -type d -not -perm -ug=w | xargs chmod ug+w
+find . -type d -not -perm -ug=w -print0 | xargs -0 chmod ug+w
 @end example
 
 @noindent
@@ -4696,7 +4696,7 @@ from its standard input and builds them into command lines.  We can
 use it like this:
 
 @smallexample
-find /var/tmp/stuff -mtime +90 -print | xargs /bin/rm
+find /var/tmp/stuff -mtime +90 -print0 | xargs -0 /bin/rm
 @end smallexample
 
 For example if the files found by @code{find} are
@@ -5586,7 +5586,7 @@ actions are just as insecure as @samp{-exec}.
 In fact, since the construction
 
 @example
-find @dots{}  -print | xargs @enddots{}
+find @dots{} -print | xargs @enddots{}
 @end example
 
 does not cope correctly with newlines or other ``white space'' in
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.