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