]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
env: add --chdir option
authorColin Watson <cjwatson@debian.org>
Sun, 27 Aug 2017 07:16:21 +0000 (08:16 +0100)
committerPádraig Brady <P@draigBrady.com>
Tue, 29 Aug 2017 07:38:19 +0000 (00:38 -0700)
This is useful when chaining with other commands that run commands in a
different context, while avoiding using the shell to cd, and thus
having to consider shell quoting the chained command.

* NEWS (New features): Document the new option.
* doc/coreutils.texi (env invocation): Likewise.
* src/env.c (usage): Likewise.
(main): Implement the new option.
* tests/misc/env.sh: Test the new option.

NEWS
doc/coreutils.texi
src/env.c
tests/misc/env.sh

diff --git a/NEWS b/NEWS
index 4c63397e7ce153e9c83092320c27baa404cc0998..e6e732caf3eaf68525a269371ccb6c90a790afd8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -88,6 +88,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   split supports a new --hex-suffixes[=from] option to create files with
   lower case hexadecimal suffixes, similar to the --numeric-suffixes option.
 
+  env now has a --chdir (-C) option to change the working directory before
+  executing the subsidiary program.
+
   expr supports multibyte strings for all string operations.
 
 ** Improvements
index 84b03f26d11728aff2d8075266f3f83062793f5a..5c424c166fb2233c64bad125e7329466abea356d 100644 (file)
@@ -16968,6 +16968,25 @@ environment.
 @opindex --ignore-environment
 Start with an empty environment, ignoring the inherited environment.
 
+@item -C @var{dir}
+@itemx --chdir=@var{dir}
+@opindex -C
+@opindex --chdir
+Change the working directory to @var{dir} before invoking @var{command}.
+This differs from the shell built-in @command{cd} in that it starts
+@var{command} as a subprocess rather than altering the shell's own working
+directory; this allows it to be chained with other commands that run commands
+in a different context.  For example:
+
+@example
+# Run 'true' with /chroot as its root directory and /srv as its working
+# directory.
+chroot /chroot env --chdir=/srv true
+# Run 'true' with /build as its working directory, FOO=bar in its
+# environment, and a time limit of five seconds.
+env --chdir=/build FOO=bar timeout 5 true
+@end example
+
 @end table
 
 @cindex exit status of @command{env}
index 63d5c2c2cbcbcf7cc945e482c522b77ac0fcde37..38769f81346df69b6e59396342032d94c6f4b55b 100644 (file)
--- a/src/env.c
+++ b/src/env.c
@@ -38,6 +38,7 @@ static struct option const longopts[] =
   {"ignore-environment", no_argument, NULL, 'i'},
   {"null", no_argument, NULL, '0'},
   {"unset", required_argument, NULL, 'u'},
+  {"chdir", required_argument, NULL, 'C'},
   {GETOPT_HELP_OPTION_DECL},
   {GETOPT_VERSION_OPTION_DECL},
   {NULL, 0, NULL, 0}
@@ -63,6 +64,9 @@ Set each NAME to VALUE in the environment and run COMMAND.\n\
   -i, --ignore-environment  start with an empty environment\n\
   -0, --null           end each output line with NUL, not newline\n\
   -u, --unset=NAME     remove variable from the environment\n\
+"), stdout);
+      fputs (_("\
+  -C, --chdir=DIR      change working directory to DIR\n\
 "), stdout);
       fputs (HELP_OPTION_DESCRIPTION, stdout);
       fputs (VERSION_OPTION_DESCRIPTION, stdout);
@@ -81,6 +85,7 @@ main (int argc, char **argv)
   int optc;
   bool ignore_environment = false;
   bool opt_nul_terminate_output = false;
+  char const *newdir = NULL;
 
   initialize_main (&argc, &argv);
   set_program_name (argv[0]);
@@ -91,7 +96,7 @@ main (int argc, char **argv)
   initialize_exit_failure (EXIT_CANCELED);
   atexit (close_stdout);
 
-  while ((optc = getopt_long (argc, argv, "+iu:0", longopts, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "+C:iu:0", longopts, NULL)) != -1)
     {
       switch (optc)
         {
@@ -103,6 +108,9 @@ main (int argc, char **argv)
         case '0':
           opt_nul_terminate_output = true;
           break;
+        case 'C':
+          newdir = optarg;
+          break;
         case_GETOPT_HELP_CHAR;
         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
         default:
@@ -120,7 +128,7 @@ main (int argc, char **argv)
     }
 
   optind = 0;                  /* Force GNU getopt to re-initialize. */
-  while ((optc = getopt_long (argc, argv, "+iu:0", longopts, NULL)) != -1)
+  while ((optc = getopt_long (argc, argv, "+C:iu:0", longopts, NULL)) != -1)
     if (optc == 'u' && unsetenv (optarg))
       die (EXIT_CANCELED, errno, _("cannot unset %s"), quote (optarg));
 
@@ -139,19 +147,34 @@ main (int argc, char **argv)
       optind++;
     }
 
-  /* If no program is specified, print the environment and exit. */
-  if (argc <= optind)
+  bool program_specified = optind < argc;
+
+  if (opt_nul_terminate_output && program_specified)
     {
+      error (0, 0, _("cannot specify --null (-0) with command"));
+      usage (EXIT_CANCELED);
+    }
+
+  if (newdir && ! program_specified)
+    {
+      error (0, 0, _("must specify command with --chdir (-C)"));
+      usage (EXIT_CANCELED);
+    }
+
+  if (! program_specified)
+    {
+      /* Print the environment and exit. */
       char *const *e = environ;
       while (*e)
         printf ("%s%c", *e++, opt_nul_terminate_output ? '\0' : '\n');
       return EXIT_SUCCESS;
     }
 
-  if (opt_nul_terminate_output)
+  if (newdir)
     {
-      error (0, errno, _("cannot specify --null (-0) with command"));
-      usage (EXIT_CANCELED);
+      if (chdir (newdir) != 0)
+        die (EXIT_CANCELED, errno, _("cannot change directory to %s"),
+             quoteaf (newdir));
     }
 
   execvp (argv[optind], &argv[optind]);
index f2f6ba8df6c194d0a7143c0465ee46e0278f9ffd..fc589cccbc3862707aa215546e35565789b2ee53 100755 (executable)
@@ -18,7 +18,7 @@
 
 
 . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ env
+print_ver_ env pwd
 
 # A simple shebang program to call "echo" from symlinks like "./-u" or "./--".
 echo "#!$abs_top_builddir/src/echo simple_echo" > simple_echo \
@@ -150,4 +150,17 @@ test "x$(sh -c '\c=d echo fail')" = xpass && #dash 0.5.4 fails so check first
 returns_ 125 env -u a=b true || fail=1
 returns_ 125 env -u '' true || fail=1
 
+# Verify changing directory.
+mkdir empty || framework_failure_
+returns_ 125 env --chdir=empty/nonexistent true || fail=1
+returns_ 125 env -C empty 2>out || fail=1
+printf '%s\n' \
+  'env: must specify command with --chdir (-C)' \
+  "Try 'env --help' for more information." > exp ||
+  framework_failure_
+compare exp out || fail=1
+exp=$(cd empty && env pwd) || framework_failure_
+got=$(env --chdir=empty pwd) || fail=1
+test "$exp" = "$got" || fail=1
+
 Exit $fail