On 13/08/2026 05:34, Collin Funk wrote:
Pádraig Brady <[email protected]> writes:

This patch set adds --env0-from to the env(1) command.

https://github.com/pixelb/coreutils/compare/env0-from

This supports efficient round tripping from the output of `env -0`,
which may contain non standard environment entries without '=' etc.

Note the naming was chosen to be descriptive and consistent form
with the existing --files0-from option in various commands.

The trigger for doing this now was the request to support regex filtering
of environment entries, which can now be achieved more generally
and robustly with something like:

   env -i --env0-from=<( env -0 | sed -z ... )

For textual environment files, one can leverage sh to parse and exec,
which gives support for comments, interpolation etc. which there
is no need for env(1) to re-implement, though a subset of that
functionality is available through the -S option if required.

Nice, it works well with more environment variables than you could ever need:

     $ for i in {0..50000}; do export ENV$i=$i; done
     $ env -0 > .env
     $ time ./src/env -i --env0-from=<(cat $(yes .env | head -n 1000)) \
         >/dev/null
     real    0m15.132s
     user    0m14.314s
     sys     0m1.159s


Cool. BTW a lot of that overhead is in the existing quoting of output:

  $ printf "USUAL_%d=%032d\0" $(seq 50000) > .env
  $ for rawout in '' '-0'; do
      time src/env $rawout \
       -i --env0-from <(cat $(yes .env | head -n 1000)) > /dev/null
    done

  real  0m9.662s
  user  0m8.277s
  sys   0m1.601s

  real  0m2.369s
  user  0m1.115s
  sys   0m1.490s

Some minor things:

+/* Return true if ENTRY is an assignment whose name is NAME.  */
+static bool
+entry_has_name (char const *entry, char const *name, idx_t name_length)
+{
+  char const *eq = strchr (entry, '=');
+  return (eq && eq - entry == name_length
+          && memcmp (entry, name, name_length) == 0);
+}

It's probably better to use memeq for consistency:

     $ git grep --perl-regexp 'memcmp *\(\S+?, *\S+?, *\S+?\) == 0' | wc -l
     1
     $ git grep --perl-regexp 'memeq *\(\S+?, *\S+?, *\S+?\)' | wc -l
     11

Indeed. I also fixed that one instance in cut.c and added a syntax check:

+# Prefer memeq() to memcmp() where appropriate
+sc_prohibit_memcmp:
+       @prohibit='^[^#].*memcmp *\(.*= 0'                              \
+       halt='use memeq() instead of memcmp() =='                       \
+         $(_sc_search_regexp)


+/* Hash an environment vector slot by the name in its assignment.  */
+static size_t
+env_vector_slot_hash (void const *x, size_t table_size)
+{
+  char *const *slot = x;
+  char const *entry = *slot;
+  char const *eq = strchr (entry, '=');
+  size_t value = 0;
+  for (char const *p = entry; p < eq; ++p)
+    value = value * 31 + (unsigned char) *p;
+  return value % table_size;
+}

I was going to say we could just use lib/hashcode-string1.c, but I guess
Gnulib doesn't have a function for hashing N bytes of memory instead of
NUL terminated strings. I would have thought we had a need for it
somewhere.
There is the hashcode-mem module actually.
I've updated to using that rather than proliferating hash routines.
I was tempted to write a temp NUL and use hash_string(),
but that seemed a bit messy in comparison.

cheers,
Padraig

Reply via email to