M STATUS => STATUS +1 -1
@@ 43,7 43,7 @@ T diff
T du
T echo
T ed*
-T env
+ D env
W ex
T expand
T expr
M doc/ctools.7.scd => doc/ctools.7.scd +2 -0
@@ 37,6 37,8 @@ shell environment. These tools are used for tasks such as:
: Compare two sorted files
| *dirname*(1)
: Print the directory part of a pathname
+| *env*(1)
+: Run command with a specified environment
| *false*(1)
: Exit with status code 1
| *head*(1)
A doc/env.1.scd => doc/env.1.scd +44 -0
@@ 0,0 1,44 @@
+env(1) "ctools"
+
+# NAME
+
+env - set the environment to run a program
+
+# SYNOPSIS
+
+*env* [-i] [_name_=_value_]... [_utility_ [_argument_...]]
+
+# DESCRIPTION
+
+*env* will update the environment based on its arguments, then invoke
+_utility_, passing _arguments_ to it.
+
+If _utility_ is not provided, the resulting environment is written to standard
+output with one _name_=_value_ pair per line.
+
+# OPTIONS
+
+*-i*
+ Clear the inherited environment before setting new variables and invoking
+ _utility_.
+
+# UNSPECIFIED BEHAVIOR
+
+The POSIX standard does not unambiguously specify the behavior of this command
+under certain conditions. Under such conditions, the ctools implementation of
+*env* behaves as follows:
+
+- If the first argument is *'-'*, *env* will exit with an error.
+
+# NOTES
+
+The ctools implementation of *env* uses _execvp_ to execute _utility_. Should
+PATH be unset, _execvp_ may search some default path set by the system and/or
+C library.
+
+# DISCLAIMER
+
+This command is part of ctools and is compatible with POSIX-1.2017, and may
+optionally support XSI extensions. This man page is not intended to be a
+complete reference, and where it disagrees with the specification, the
+specification takes precedence.
M doc/meson.build => doc/meson.build +1 -0
@@ 13,6 13,7 @@ man_files = [
'cmp.1',
'comm.1',
'dirname.1',
+ 'env.1',
'false.1',
'head.1',
'link.1',
M meson.build => meson.build +1 -0
@@ 20,6 20,7 @@ oneshots = [
'cmp',
'comm',
'dirname',
+ 'env',
'false',
'head',
'logname',
A src/env.c => src/env.c +78 -0
@@ 0,0 1,78 @@
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+extern char **environ;
+
+static void
+usage(void)
+{
+ fputs("env [-i] [name=value]... [utility [argument...]]\n", stderr);
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc > 1 && strcmp(argv[1], "-") == 0) {
+ usage();
+ return 1;
+ }
+
+ char opt;
+ bool clear = false;
+ while ((opt = getopt(argc, argv, "i")) != -1) {
+ switch (opt) {
+ case 'i':
+ clear = true;
+ break;
+ default:
+ usage();
+ return 1;
+ }
+ }
+
+ if (clear) {
+ environ = NULL;
+ }
+
+ argc = argc - optind;
+ argv = &argv[optind];
+
+ int cmd_idx = 0;
+ for (; cmd_idx < argc; cmd_idx++) {
+ if (!strchr(argv[cmd_idx], '=')) {
+ break;
+ }
+ }
+
+ for (int i = 0; i < cmd_idx; i++) {
+ if (putenv(argv[i]) != 0) {
+ perror("env");
+ return 1;
+ }
+ }
+
+ /* No command, print the env */
+ if (cmd_idx == argc) {
+ for (int i = 0; environ[i]; i++) {
+ puts(environ[i]);
+ }
+ return 0;
+ }
+
+ argv = &argv[cmd_idx];
+
+ execvp(argv[0], argv);
+
+ /* if exec returns, it's errored */
+ fprintf(stderr, "env: '%s': %s\n", argv[0], strerror(errno));
+ switch (errno) {
+ case ENOENT:
+ return 127;
+ default:
+ return 126;
+ }
+}
A test/env => test/env +58 -0
@@ 0,0 1,58 @@
+#!/bin/sh
+tool=env
+. "$HARNESS"
+
+export FOO=bar
+export BAZ=quux
+
+echo ls > "$TMPDIR"/test-script
+
+should_clear_env() (
+ ev="$(env -i)"
+ [ -z "$ev" ]
+)
+
+should_print_env() (
+ ev="$(env | grep '^FOO')"
+ [ "$ev" = "FOO=bar" ]
+ ev="$(env | grep '^BAZ')"
+ [ "$ev" = "BAZ=quux" ]
+)
+
+should_invoke_cmd() (
+ ev="$(env basename a/b/c.d)"
+ [ "$ev" = "c.d" ]
+)
+
+should_invoke_cmd_clean_env() (
+ ev=$(env -i BAR=foo env)
+ [ "$ev" = "BAR=foo" ]
+)
+
+should_invoke_cmd_with_options() (
+ ev=$(largefile | env -i PATH="$PATH" head -n 15)
+ largefile_head=$(largefile | head -n 15)
+ [ "$ev" = "$largefile_head" ]
+)
+
+should_fail_127() (
+ ev="$(env program-does-not-exist 2> /dev/null)"
+ [ $? = 127 ] && [ -z "$ev" ]
+)
+
+should_fail_126() (
+ ev="$(env "$TMPDIR"/test-script 2> /dev/null)"
+ [ $? = 126 ] && [ -z "$ev" ]
+)
+
+should_handle_ddash env cat /dev/null
+
+runtests \
+ should_handle_ddash \
+ should_clear_env \
+ should_print_env \
+ should_invoke_cmd \
+ should_invoke_cmd_clean_env \
+ should_invoke_cmd_with_options \
+ should_fail_127 \
+ should_fail_126
M test/meson.build => test/meson.build +1 -0
@@ 10,6 10,7 @@ test_files = [
'cmp',
'comm',
'dirname',
+ 'env',
'false',
'head',
'logname',