printenv are printing the list of environment strings (meant to contain environment variable definitions) that is given to them by the command that executes them. The caller will eventually do a:
execve("/usr/bin/env", argv, envp);
system call where
envp are two lists of strings.
printenv just print the list of strings in
envp, one per line.
By convention, the strings in
envp are in the format
var=value, but they don't have to be (I don't know of any
execve() implementation that enforces it) and most
printenv implementations don't care when they display them.
When the caller is a POSIX shell, it will include in the
envp that it passes to
env the list of its shell variables that are marked for export (either because the user called
typeset -x on it, or because the variable was already in the environment that the shell received on start-up).
If some of the environment variables that the shell received on start-up could not be mapped to a shell variable, or if any of the
envp strings it received didn't contain a
= character, depending on the shell implementation, those strings will be passed along untouched, or the shell will strip them or some of them.
bash, using GNU
env to pass a list of arbitrary variable names (
env can't pass arbitrary envp strings though, they have to contain a
=, and the ones that use
setenv() can't pass some that start with
$ env -i '=foo' '1=x' '+=y' bash -c printenv
(the variable with the empty name was removed but not the other ones).
Also, if the shell received multiple
envp strings for the same variable name, depending on the shell, they will all be passed along, or only the first one, or only the last one.
set in POSIX shells prints the list of shell variables, including non-scalar ones for shells that support array/hash types, whether they've been marked for export or not.
In POSIX shells you can also use
export -p to list the variables that have been marked for export. Contrary to
printenv, that also lists variables that have been marked for export but not be given any value yet.
In Korn-like shells like
bash, you can also use
typeset to get more information including attributes of variables, and list variables by type (like
typeset -a to list the array variables).
Here, by adding
USER_ENVI=10 to your
~/.bashrc, you're configuring the interactive non-login invocations of the
bash shell to define a
USER_ENVI shell variable on start-up. Since you've not used
export, that variable stays a shell variable (unless it was in the environment when
bash started), so it's not passed as environment variables to commands executed by that shell.
/etc/environment, itself, on Ubuntu 16.04 is read by the
pam_env.so pluggable authentication module. Applications that log you in like
lightdm will read those files if configured to use
/etc/pam.d and pass the corresponding environment variables (nothing to do with shell variables here) to the command they start in your name after you authenticate (like your login shell for
sshd, or your graphical session manager for
Since the environment is inherited by default, when your session manager executes a terminal emulator which in turn executes your login shell, those environment variables will be passed along at each step, and your shell will map them to shell variables which you can expand in command line with things like
pam_env env files like
/etc/environment look like shell scripts, but
pam_env doesn't invoke a shell to parse them and understands only a subset of the shell syntax and only allows defining variables whose name is made of one or more ASCII alpha-numeric characters or underscores (it does let you define a
123 variable though which is not a valid POSIX shell variable name).
¹, to pass a list of arbitrary env strings, you can also call
execve() directly like with:
perl -e 'require "syscall.ph";
$cmd = "/bin/zsh";
$args = pack("p*x[p]", "sh", "-c", "printenv");
$env = pack("p*x[p]", "a=b", "a=c", "", "+=+", "=foo", "bar");
syscall(SYS_execve(), $cmd, $args, $env)'
here testing with
zsh instead of