[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] ls: adding --zero/-z option, including tests

From: Anthon van der Neut
Subject: [PATCH] ls: adding --zero/-z option, including tests
Date: Mon, 03 Feb 2014 16:15:51 +0100
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.2.0

A recent Q&A thread on on the reason why ls has no option
like find to generate NUL separated output triggered the making of this patch.
( ).

Quite often questions pop up on that site where questioners, unaware of the
fact that newlines and other special characters can be part of filenames do
something like:
    ls -rt | xargs ...
Invariably someone points out that it is not safe to process the output of ls in
this way in general.

The included patch adds an option --zero/-z to ls so that the filenames
are separated by the NUL character instead of a newline when doing the -1
(which is in principle indistinguishable from a newline that is part of the
filename as you can have "abc", "def" and "abc\ndef" as filenames in one

The patch allows one to do:
    ls -rt --zero | xargs -0 ...
|||     ls -rtz | xargs -0 ...
One file with basic tests has been added, another extended.

Please let me know if this is not acceptable, or if changes are
required before this can be processed.


- Patch
- Developer Certificate of origin


From 6413d5e2a488ecadb8b988c802fe0a5e5cb7d8f4 Mon Sep 17 00:00:00 2001
From: Anthon van der Neut <address@hidden>
Date: Mon, 3 Feb 2014 15:33:50 +0100
Subject: [PATCH] ls: adding --zero/-z option, including tests

* src/ls.c has the necessary changes to allow -z/--zero option to be
  specified, resulting in a NUL seperated list of files. This
  allows the output of e.g. "ls -rtz" to be piped into other programs

* tests/ls/ was extended to test the -z option

* test/ls/ was added to test both the long and short option
  together with "-t"

This patch was inspired by numerous questions on
where the output of ls was piped into some other program, invariably
resulting in someone pointing out that is an unsafe practise because of
possible newlines and other characters in the filenames.
 src/ls.c            |   31 +++++++++++++++++++++++++------
 tests/ls/  |    7 ++++++-
 tests/ls/ |   38 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 69 insertions(+), 7 deletions(-)
 create mode 100755 tests/ls/

diff --git a/src/ls.c b/src/ls.c
index 5d87dd3..962e6bb 100644
--- a/src/ls.c
+++ b/src/ls.c
@@ -381,6 +381,7 @@ static int file_size_width;
    many_per_line for just names, many per line, sorted vertically.
    horizontal for just names, many per line, sorted horizontally.
    with_commas for just names, many per line, separated by commas.
+   with_zero for just names, one per line, separated by NUL.
-l (and other options that imply -l), -1, -C, -x and -m control
    this parameter.  */
@@ -391,7 +392,8 @@ enum format
     one_per_line,              /* -1 */
     many_per_line,             /* -C */
     horizontal,                        /* -x */
-    with_commas                        /* -m */
+    with_commas,               /* -m */
+    with_zero,                 /* -z */
static enum format format;
@@ -842,6 +844,7 @@ static struct option const long_options[] =
   {"block-size", required_argument, NULL, BLOCK_SIZE_OPTION},
   {"context", no_argument, 0, 'Z'},
   {"author", no_argument, NULL, AUTHOR_OPTION},
+  {"zero", no_argument, NULL, 'z'},
   {NULL, 0, NULL, 0}
@@ -850,12 +853,12 @@ static struct option const long_options[] =
 static char const *const format_args[] =
   "verbose", "long", "commas", "horizontal", "across",
-  "vertical", "single-column", NULL
+  "vertical", "single-column", "zero", NULL
 static enum format const format_types[] =
   long_format, long_format, with_commas, horizontal, horizontal,
-  many_per_line, one_per_line
+  many_per_line, one_per_line, with_zero
 ARGMATCH_VERIFY (format_args, format_types);
@@ -1645,7 +1648,7 @@ decode_switches (int argc, char **argv)
       int oi = -1;
       int c = getopt_long (argc, argv,
-                           "abcdfghiklmnopqrstuvw:xABCDFGHI:LNQRST:UXZ1",
+                           "abcdfghiklmnopqrstuvw:xzABCDFGHI:LNQRST:UXZ1",
                            long_options, &oi);
       if (c == -1)
@@ -1852,6 +1855,10 @@ decode_switches (int argc, char **argv)
             format = one_per_line;
+ case 'z':
+          format = with_zero;
+          break;
         case AUTHOR_OPTION:
           print_author = true;
@@ -2607,7 +2614,8 @@ print_dir (char const *name, char const *realname, bool 
                  ls uses constant memory while processing the entries of
                  this directory.  Useful when there are many (millions)
                  of entries in a directory.  */
-              if (format == one_per_line && sort_type == sort_none
+              if ((format == one_per_line || format == with_zero)
+                      && sort_type == sort_none
                       && !print_block_size && !recursive)
                   /* We must call sort_files in spite of
@@ -3598,6 +3606,14 @@ print_current_files (void)
+ case with_zero:
+      for (i = 0; i < cwd_n_used; i++)
+        {
+          print_file_name_and_frills (sorted_file[i], 0);
+          putchar ('\0');
+        }
+      break;
     case many_per_line:
       print_many_per_line ();
@@ -4490,6 +4506,7 @@ print_many_per_line (void)
           indent (pos + name_length, pos + max_name_length);
           pos += max_name_length;
+      putchar ('X'); // AvdN
       putchar ('\n');
@@ -4780,7 +4797,8 @@ Sort entries alphabetically if none of -cftuvSUX nor 
--sort is specified.\n\
   -F, --classify             append indicator (one of */=>@|) to entries\n\
       --file-type            likewise, except do not append '*'\n\
       --format=WORD          across -x, commas -m, horizontal -x, long -l,\n\
-                               single-column -1, verbose -l, vertical -C\n\
+                               single-column -1, verbose -l, vertical -C,\n\
+                               zeros -z\n\
       --full-time            like -l --time-style=full-iso\n\
 "), stdout);
       fputs (_("\
@@ -4888,6 +4906,7 @@ Sort entries alphabetically if none of -cftuvSUX nor 
--sort is specified.\n\
   -X                         sort alphabetically by entry extension\n\
   -Z, --context              print any security context of each file\n\
   -1                         list one file per line\n\
+  -z, --zero                 list files separated with NUL\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
diff --git a/tests/ls/ b/tests/ls/
index e356a29..da28b96 100755
--- a/tests/ls/
+++ b/tests/ls/
@@ -30,11 +30,16 @@ out
 ls -1 > out || fail=1
compare exp out || fail=1 +/bin/echo -en "dir\00exp\00out\00symlink\00" > exp || framework_failure_
+ls --zero > out || fail=1
+compare exp out || fail=1
 cat > exp <<\EOF
diff --git a/tests/ls/ b/tests/ls/
new file mode 100755
index 0000000..cdbd311
--- /dev/null
+++ b/tests/ls/
@@ -0,0 +1,38 @@
+# Make sure name is used as secondary key when sorting on mtime or ctime.
+# Copyright (C) 1998-2014 Free Software Foundation, Inc.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# GNU General Public License for more details.
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <>.
+. "${srcdir=.}/tests/"; path_prepend_ ./src
+print_ver_ ls touch
+touch -d "$date" c || framework_failure_
+touch -d "$date" a || framework_failure_
+touch -d "$date" b || framework_failure_
+ls -zt a b c > out || fail=1
+/bin/echo -en "a\00b\00c\00" > exp
+compare exp out || fail=1
+rm -rf out exp
+ls -rt --zero a b c > out || fail=1
+/bin/echo -en "c\00b\00a\00" > exp
+compare exp out || fail=1
+Exit $fail


        Developer's Certificate of Origin 1.1

        By making a contribution to this project, I certify that:

        (a) The contribution was created in whole or in part by me and I
            have the right to submit it under the open source license
            indicated in the file; or

        (b) The contribution is based upon previous work that, to the best
            of my knowledge, is covered under an appropriate open source
            license and I have the right under that license to submit that
            work with modifications, whether created in whole or in part
            by me, under the same open source license (unless I am
            permitted to submit under a different license), as indicated
            in the file; or

        (c) The contribution was provided directly to me by some other
            person who certified (a), (b) or (c) and I have not modified

        (d) I understand and agree that this project and the contribution
            are public and that a record of the contribution (including all
            personal information I submit with it, including my sign-off) is
            maintained indefinitely and may be redistributed consistent with
            this project or the open source license(s) involved.

        Signed-off-by: Anthon van der Neut <address@hidden>


reply via email to

[Prev in Thread] Current Thread [Next in Thread]