From ccc1d877f5242b0efece4ef4ccab83f37e4bb222 Mon Sep 17 00:00:00 2001 From: Christopher Vittal Date: Wed, 25 Sep 2019 12:21:39 -0400 Subject: [PATCH] Implement env --- STATUS | 2 +- doc/ctools.7.scd | 2 ++ doc/env.1.scd | 44 +++++++++++++++++++++++++++ doc/meson.build | 1 + meson.build | 1 + src/env.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ test/env | 58 +++++++++++++++++++++++++++++++++++ test/meson.build | 1 + 8 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 doc/env.1.scd create mode 100644 src/env.c create mode 100755 test/env diff --git a/STATUS b/STATUS index 7559016..a860565 100644 --- a/STATUS +++ b/STATUS @@ -43,7 +43,7 @@ T diff T du T echo T ed* -T env + D env W ex T expand T expr diff --git a/doc/ctools.7.scd b/doc/ctools.7.scd index 0700d69..726ae48 100644 --- a/doc/ctools.7.scd +++ b/doc/ctools.7.scd @@ -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) diff --git a/doc/env.1.scd b/doc/env.1.scd new file mode 100644 index 0000000..bb6b41a --- /dev/null +++ b/doc/env.1.scd @@ -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. diff --git a/doc/meson.build b/doc/meson.build index 1d4c346..48097c9 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -13,6 +13,7 @@ man_files = [ 'cmp.1', 'comm.1', 'dirname.1', + 'env.1', 'false.1', 'head.1', 'link.1', diff --git a/meson.build b/meson.build index c0c37d5..6d8d2ce 100644 --- a/meson.build +++ b/meson.build @@ -20,6 +20,7 @@ oneshots = [ 'cmp', 'comm', 'dirname', + 'env', 'false', 'head', 'logname', diff --git a/src/env.c b/src/env.c new file mode 100644 index 0000000..db86b97 --- /dev/null +++ b/src/env.c @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include + +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; + } +} diff --git a/test/env b/test/env new file mode 100755 index 0000000..7ee2ef0 --- /dev/null +++ b/test/env @@ -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 diff --git a/test/meson.build b/test/meson.build index eeb57d7..10f9288 100644 --- a/test/meson.build +++ b/test/meson.build @@ -10,6 +10,7 @@ test_files = [ 'cmp', 'comm', 'dirname', + 'env', 'false', 'head', 'logname', -- 2.30.1