From 30ec7a6ade71fbed5589738a87ef70fd7fd1ec4d Mon Sep 17 00:00:00 2001 From: Colin Watson Date: Sun, 27 Aug 2017 08:16:21 +0100 Subject: [PATCH] env: add --chdir option 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 | 3 +++ doc/coreutils.texi | 19 +++++++++++++++++++ src/env.c | 37 ++++++++++++++++++++++++++++++------- tests/misc/env.sh | 15 ++++++++++++++- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/NEWS b/NEWS index d37195e..65f99b7 100644 --- a/NEWS +++ b/NEWS @@ -84,6 +84,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 diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 8f1cb4c..bd34acf 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -16909,6 +16909,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} diff --git a/src/env.c b/src/env.c index 63d5c2c..988b14d 100644 --- 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} @@ -64,6 +65,9 @@ Set each NAME to VALUE in the environment and run COMMAND.\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); fputs (_("\ @@ -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, errno, _("cannot specify --null (-0) with command")); + usage (EXIT_CANCELED); + } + + if (newdir && ! program_specified) + { + error (0, errno, _("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]); diff --git a/tests/misc/env.sh b/tests/misc/env.sh index f2f6ba8..fc589cc 100755 --- a/tests/misc/env.sh +++ b/tests/misc/env.sh @@ -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 -- 2.9.3