env, printenv: add -0/--null option
authorEric Blake <ebb9@byu.net>
Tue, 27 Oct 2009 12:36:40 +0000 (06:36 -0600)
committerEric Blake <ebb9@byu.net>
Wed, 28 Oct 2009 01:55:35 +0000 (19:55 -0600)
Allows for unambiguous processing when environment values (or even
non-portable names!) contain newline.

* src/env.c (longopts): Add new option.
(usage): Document it.
(main): Implement it.
* src/printenv.c (longopts): New variable.
(usage): Document new option.
(main): Implement it.
* doc/coreutils.texi (Common options): New macro optNull.
(du invocation, env invocation, printenv invocation): Use it.
* NEWS: Mention this.
* tests/misc/env-null: New test.
* tests/Makefile.am (TESTS): Run it.

NEWS
doc/coreutils.texi
src/env.c
src/printenv.c
tests/Makefile.am
tests/misc/env-null [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index abbeb27..442ce13 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -44,6 +44,9 @@ GNU coreutils NEWS                                    -*- outline -*-
 
 ** New features
 
+  env and printenv now accept the option --null (-0), as a means to
+  avoid ambiguity with newlines embedded in the environment.
+
   md5sum --check now also accepts openssl-style checksums.
   So do sha1sum, sha224sum, sha384sum and sha512sum.
 
index 138cf01..c5f5135 100644 (file)
@@ -574,6 +574,18 @@ Do not treat the last operand specially when it is a directory or a
 symbolic link to a directory.  @xref{Target directory}.
 @end macro
 
+@macro optNull{cmd}
+@item -0
+@opindex -0
+@itemx --null
+@opindex --null
+@cindex output @sc{nul}-byte-terminated lines
+Output a zero byte (@acronym{ASCII} @sc{nul}) at the end of each line,
+rather than a newline. This option enables other programs to parse the
+output of @command{\cmd\} even when that output would contain data
+with embedded newlines.
+@end macro
+
 @macro optSi
 @itemx --si
 @opindex --si
@@ -10376,15 +10388,7 @@ Show the total for each directory (and file if --all) that is at
 most MAX_DEPTH levels down from the root of the hierarchy.  The root
 is at level 0, so @code{du --max-depth=0} is equivalent to @code{du -s}.
 
-@item -0
-@opindex -0
-@itemx --null
-@opindex --null
-@cindex output null-byte-terminated lines
-Output a zero byte (@acronym{ASCII} @sc{nul}) at the end of each line,
-rather than a newline. This option enables other programs to parse the
-output of @command{du} even when that output would contain file names
-with embedded newlines.
+@optNull{du}
 
 @optSi
 
@@ -12806,8 +12810,13 @@ If no @var{variable}s are specified, @command{printenv} prints the value of
 every environment variable.  Otherwise, it prints the value of each
 @var{variable} that is set, and nothing for those that are not set.
 
-The only options are a lone @option{--help} or @option{--version}.
-@xref{Common options}.
+The program accepts the following option.  Also see @ref{Common options}.
+
+@table @samp
+
+@optNull{printenv}
+
+@end table
 
 @cindex exit status of @command{printenv}
 Exit status:
@@ -14438,6 +14447,8 @@ Options must precede operands.
 
 @table @samp
 
+@optNull{env}
+
 @item -u @var{name}
 @itemx --unset=@var{name}
 @opindex -u
index 8d7d55e..c20b057 100644 (file)
--- a/src/env.c
+++ b/src/env.c
         Unset variable VARIABLE (remove it from the environment).
         If VARIABLE was not set, does nothing.
 
+   -0
+   --null
+        When no program is specified, output environment information
+        terminated by NUL bytes rather than newlines.
+
    variable=value (an arg containing a "=" character)
         Set the environment variable VARIABLE to value VALUE.  VALUE
         may be of zero length ("variable=").  Setting a variable to a
 static struct option const longopts[] =
 {
   {"ignore-environment", no_argument, NULL, 'i'},
+  {"null", no_argument, NULL, '0'},
   {"unset", required_argument, NULL, 'u'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
@@ -116,8 +122,9 @@ Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
       fputs (_("\
 Set each NAME to VALUE in the environment and run COMMAND.\n\
 \n\
-  -i, --ignore-environment   start with an empty environment\n\
-  -u, --unset=NAME           remove variable from the environment\n\
+  -i, --ignore-environment  start with an empty environment\n\
+  -0, --null           end each output line with 0 byte rather than newline\n\
+  -u, --unset=NAME     remove variable from the environment\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -135,6 +142,7 @@ main (int argc, char **argv)
 {
   int optc;
   bool ignore_environment = false;
+  bool opt_nul_terminate_output = false;
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -145,7 +153,7 @@ main (int argc, char **argv)
   initialize_exit_failure (EXIT_CANCELED);
   atexit (close_stdout);
 
-  while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "+iu:0", longopts, NULL)) != -1)
     {
       switch (optc)
         {
@@ -154,6 +162,9 @@ main (int argc, char **argv)
           break;
         case 'u':
           break;
+        case '0':
+          opt_nul_terminate_output = true;
+          break;
         case_GETOPT_HELP_CHAR;
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
         default:
@@ -171,7 +182,7 @@ main (int argc, char **argv)
     }
 
   optind = 0;                  /* Force GNU getopt to re-initialize. */
-  while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "+iu:0", longopts, NULL)) != -1)
     if (optc == 'u' && unsetenv (optarg))
       error (EXIT_CANCELED, errno, _("cannot unset %s"), quote (optarg));
 
@@ -191,10 +202,16 @@ main (int argc, char **argv)
     {
       char *const *e = environ;
       while (*e)
-        puts (*e++);
+        printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
       exit (EXIT_SUCCESS);
     }
 
+  if (opt_nul_terminate_output)
+    {
+      error (0, errno, _("cannot specify --null (-0) with command"));
+      usage (EXIT_CANCELED);
+    }
+
   execvp (argv[optind], &argv[optind]);
 
   {
index dcdcb83..6bc03f4 100644 (file)
@@ -45,6 +45,14 @@ enum { PRINTENV_FAILURE = 2 };
   proper_name ("David MacKenzie"), \
   proper_name ("Richard Mlynarik")
 
+static struct option const longopts[] =
+{
+  {"null", no_argument, NULL, '0'},
+  {GETOPT_HELP_OPTION_DECL},
+  {GETOPT_VERSION_OPTION_DECL},
+  {NULL, 0, NULL, 0}
+};
+
 void
 usage (int status)
 {
@@ -54,13 +62,15 @@ usage (int status)
   else
     {
       printf (_("\
-Usage: %s [VARIABLE]...\n\
-  or:  %s OPTION\n\
+Usage: %s [OPTION]... [VARIABLE]...\n\
 Print the values of the specified environment VARIABLE(s).\n\
 If no VARIABLE is specified, print name and value pairs for them all.\n\
 \n\
 "),
-              program_name, program_name);
+              program_name);
+      fputs (_("\
+  -0, --null     end each output line with 0 byte rather than newline\n\
+"), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
       printf (USAGE_BUILTIN_WARNING, PROGRAM_NAME);
@@ -76,6 +86,8 @@ main (int argc, char **argv)
   char *ep, *ap;
   int i;
   bool ok;
+  int optc;
+  bool opt_nul_terminate_output = false;
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -86,15 +98,24 @@ main (int argc, char **argv)
   initialize_exit_failure (PRINTENV_FAILURE);
   atexit (close_stdout);
 
-  parse_long_options (argc, argv, PROGRAM_NAME, PACKAGE_NAME, Version,
-                      usage, AUTHORS, (char const *) NULL);
-  if (getopt_long (argc, argv, "+", NULL, NULL) != -1)
-    usage (PRINTENV_FAILURE);
+  while ((optc = getopt_long (argc, argv, "+iu:0", longopts, NULL)) != -1)
+    {
+      switch (optc)
+        {
+        case '0':
+          opt_nul_terminate_output = true;
+          break;
+        case_GETOPT_HELP_CHAR;
+        case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
+        default:
+          usage (PRINTENV_FAILURE);
+        }
+    }
 
   if (optind >= argc)
     {
       for (env = environ; *env != NULL; ++env)
-        puts (*env);
+        printf ("%s%c", *env, opt_nul_terminate_output ? '\0' : '\n');
       ok = true;
     }
   else
@@ -113,7 +134,8 @@ main (int argc, char **argv)
                 {
                   if (*ep == '=' && *ap == '\0')
                     {
-                      puts (ep + 1);
+                      printf ("%s%c", ep + 1,
+                              opt_nul_terminate_output ? '\0' : '\n');
                       matched = true;
                       break;
                     }
index 731f657..eec31ae 100644 (file)
@@ -166,6 +166,7 @@ TESTS =                                             \
   misc/dircolors                               \
   misc/df                                      \
   misc/dirname                                 \
+  misc/env-null                                        \
   misc/expand                                  \
   misc/expr                                    \
   misc/factor                                  \
diff --git a/tests/misc/env-null b/tests/misc/env-null
new file mode 100755 (executable)
index 0000000..432c659
--- /dev/null
@@ -0,0 +1,69 @@
+#!/bin/sh
+# Verify behavior of env -0 and printenv -0.
+
+# Copyright (C) 2009 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
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# 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 <http://www.gnu.org/licenses/>.
+
+
+if test "$VERBOSE" = yes; then
+  set -x
+  env --version
+  env -- printenv --version
+fi
+
+. $srcdir/test-lib.sh
+
+fail=0
+
+# POSIX is clear that environ may, but need not be, sorted.
+# Environment variable values may contain newlines, which cannot be
+# observed by merely inspecting output from env.
+# Cygwin requires a minimal environment to launch new processes: execve
+# adds missing variables SYSTEMROOT and WINDIR, which show up in a
+# subsequent env.  Cygwin also requires /bin to always be part of PATH,
+# and attempts to unset or reduce PATH may cause execve to fail.
+#
+# For these reasons, it is better to compare two outputs from distinct
+# programs that should be the same, rather than building an exp file.
+env -i PATH="$PATH" env -0 > out1 || fail=1
+env -i PATH="$PATH" printenv -0 > out2 || fail=1
+compare out1 out2 || fail=1
+env -i PATH="$PATH" env --null > out2 || fail=1
+compare out1 out2 || fail=1
+env -i PATH="$PATH" printenv --null > out2 || fail=1
+compare out1 out2 || fail=1
+
+# env -0 does not work if a command is specified.
+env -0 echo hi > out
+test $? = 125 || fail=1
+test -s out && fail=1
+
+# Test env -0 on a one-variable environment.
+printf 'a=b\nc=\0' > exp || framework_failure
+env -i -0 "$(printf 'a=b\nc=')" > out || fail=1
+compare exp out || fail=1
+
+# Test printenv -0 on particular values.
+printf 'b\nc=\0' > exp || framework_failure
+env "$(printf 'a=b\nc=')" printenv -0 a > out || fail=1
+compare exp out || fail=1
+env -u a printenv -0 a > out
+test $? = 1 || fail=1
+test -s out && fail=1
+env -u b "$(printf 'a=b\nc=')" printenv -0 b a > out
+test $? = 1 || fail=1
+compare exp out || fail=1
+
+Exit $fail