commit: 958ed3917a9baad63a296dbe1067393afa32fe56
parent bcfadc38f52e4c83ce0c7f93f84983725dcd31d0
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date: Sun, 1 Sep 2024 16:49:15 +0200
cmd/env: add support for -S option
Diffstat:
3 files changed, 82 insertions(+), 7 deletions(-)
diff --git a/cmd/env.1 b/cmd/env.1
@@ -13,10 +13,18 @@
.Op Fl u Ar name
.Op Ar name Ns = Ns Ar value
.Op Ar command Op Ar argument...
+.Nm
+.Op Fl i
+.Op Fl u Ar name
+.Op Ar name Ns = Ns Ar value
+.Fl S Ar command_string
.Sh DESCRIPTION
.Nm
-with no arguments prints the environment, or if specified runs
-.Ar command .
+if specified runs
+.Ar command
+or
+.Ar command_string ,
+and otherwise prints the environment.
.Pp
The environment can be modified via the following options:
.Bl -tag -width Ds
@@ -35,6 +43,15 @@ into the new environment, it cannot itself contain the
.Qq =
character.
.El
+.Pp
+The
+.Fl S Ar command_string
+option allows to split a full command passed as a single argument
+similarly to a shell, useful for shebangs where most Unix-likes
+do not do argument splitting.
+.br
+For example:
+.Dl #!/usr/bin/env -S perl -w -T
.Sh EXIT STATUS
If
.Ar command
@@ -67,6 +84,14 @@ specification.
.Pp
The
.Fl u
-flag is an extension known to be present in FreeBSD, NetBSD, GNU coreutils, BusyBox, ...
+flag is an extension known to be present in
+.Fx ,
+.Nx ,
+GNU coreutils, BusyBox, ...
+.br
+The
+.Fl S
+flag in an extension known to be present in GNU coreutils and
+.Fx .
.Sh AUTHORS
.An Haelwenn (lanodan) Monnier Aq Mt contact+utils@hacktivis.me
diff --git a/cmd/env.c b/cmd/env.c
@@ -3,6 +3,8 @@
// SPDX-License-Identifier: MPL-2.0
#define _POSIX_C_SOURCE 200809L
+#define _XOPEN_SOURCE 800 // wordexp
+
#include <assert.h> // assert
#include <errno.h> // errno
#include <stdbool.h> // bool, true, false
@@ -10,6 +12,7 @@
#include <stdlib.h> // putenv
#include <string.h> // strchr, strerror
#include <unistd.h> // getopt, opt*
+#include <wordexp.h> // wordexp
#ifdef HAS_GETOPT_LONG
#include <getopt.h>
#endif
@@ -44,6 +47,8 @@ int
main(int argc, char *argv[])
{
bool flag_i = false;
+ bool flag_S = false;
+ wordexp_t opt_S;
int c = -1;
#ifdef HAS_GETOPT_LONG
@@ -52,14 +57,15 @@ main(int argc, char *argv[])
static struct option opts[] = {
{"ignore-environment", no_argument, 0, 'i'},
{"unset", required_argument, 0, 'u'},
+ {"split-string", required_argument, 0, 'S'},
{0, 0, 0, 0},
};
// clang-format on
// Need + as first character to get POSIX-style option parsing
- while((c = getopt_long(argc, argv, "+:iu:", opts, NULL)) != -1)
+ while((c = getopt_long(argc, argv, "+:iu:S:", opts, NULL)) != -1)
#else
- while((c = getopt(argc, argv, ":iu:")) != -1)
+ while((c = getopt(argc, argv, ":iu:S:")) != -1)
#endif
{
switch(c)
@@ -70,6 +76,33 @@ main(int argc, char *argv[])
case 'u':
unsetenv(optarg);
break;
+ case 'S':
+ flag_S = true;
+ int ret = wordexp(optarg, &opt_S, WRDE_NOCMD);
+ switch(ret)
+ {
+ case 0:
+ break;
+ case WRDE_BADCHAR:
+ fputs("env: Error: wordexpr returned WRDE_BADCHAR\n", stderr);
+ return 1;
+ case WRDE_BADVAL:
+ fputs("env: Error: Undefined shell variable (WRDE_BADVAL)\n", stderr);
+ return 1;
+ case WRDE_CMDSUB:
+ fputs("env: Error: Command substitution forbidden (WRDE_CMDSUB)\n", stderr);
+ return 1;
+ case WRDE_NOSPACE:
+ fputs("env: Error: Out of memory (WRDE_NOSPACE)\n", stderr);
+ return 1;
+ case WRDE_SYNTAX:
+ fputs("env: Error: Syntax Error (WRDE_SYNTAX)\n", stderr);
+ return 1;
+ default:
+ fprintf(stderr, "env: Unknown error %d from wordexp\n", ret);
+ return 1;
+ }
+ break;
case ':':
fprintf(stderr, "env: Error: Missing operand for option: '-%c'\n", optopt);
usage();
@@ -110,6 +143,18 @@ main(int argc, char *argv[])
}
}
+ if(flag_S)
+ {
+ errno = 0;
+ if(execvp(opt_S.we_wordv[0], opt_S.we_wordv) < 0)
+ {
+ fprintf(stderr, "env: execvp(\"%s\", ...): %s\n", opt_S.we_wordv[0], strerror(errno));
+
+ return (errno == ENOENT) ? 127 : 126;
+ }
+ assert(false);
+ }
+
if(argc < 1)
{
return do_export();
diff --git a/test-cmd/env.sh b/test-cmd/env.sh
@@ -7,8 +7,8 @@ target="${WD}/cmd/env"
. "${WD}/test_functions.sh"
-plans=9
-gentoo_sandbox && plans=5
+plans=10
+gentoo_sandbox && plans=6
. "${WD}/test-cmd/tap.sh"
t exec_true 'true' ''
@@ -21,6 +21,11 @@ t --exit=127 enoent '/var/empty/e/no/ent' 'env: execvp("/var/empty/e/no/ent", ..
t posix_me_harder "-i FOO=BAR sh -c echo" '
'
+t_args opt_S 'foo
+bar
+baz
+' "-S printf '%s\n' foo bar baz"
+
if ! gentoo_sandbox
then
t reset '-i FOO=BAR' 'FOO=BAR