1

I have seen related questions such as this but they don't provide the exact answer to my question

From my experiments as well as this answer, printenv and env pretty much show the same set of system variables.

If I set the variables in

  1. /etc/bash.bashrc (supposed to be for system-wide system variables)

    SYSTEM_ENVI=1000
    
  2. ~/.bashrc (supposed to be for user-specific system variables)

    USER_ENVI=10
    

I even logged out and logged in so the /etc/environment takes effect. The following scenario takes place:

$echo $SYSTEM_ENVI
//outputs 1000
$echo $USER_ENVI
//outputs 10
$CURR_ENVI=1
$env | grep USER_ENVI
//nothing shows up, the same if I grepped SYSTEM_ENVI or CURR_ENVI
$set | grep USER_ENVI
//shows up USER_ENVI assignment, the same if I grepped SYSTEM_ENVI or CURR_ENVI

My questions are:

  1. What system variables do printenv/env print?
  2. Should one use set to see all accessible variable (system variable and local variable) instead of printenv or env?

Regarding not duplication justification

As far as I am concern, this question and the marked answer helped me realize the facts that:

  1. Shell variables are not environment variables
  2. Assignments in /etc/bash.bashrc or ~/.bashrc don't create environment variables but rather instruct the interactive non-login-shell processes to create and initialize these shell variables on startups.

I think my question is not necessarily different from this one but reading the marked answer on that one doesn't satisfy me as much as the answer given in this post.

2 Answers2

6

env and 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 argv and envp are two lists of strings.

env/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 env, 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 export/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.

Example with 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
+=y
1=x
[...]

(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 env/printenv, that also lists variables that have been marked for export but not be given any value yet.

In Korn-like shells like ksh, zsh or 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 login, sshd, lightdm will read those files if configured to use pam_env.so in /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 login/sshd, or your graphical session manager for lightdm...).

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 echo "$VAR".

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 bash

  • FreeBSD and OpenBSD envs both reject =foo, because the underlying setenv() library call does. – JdeBP Sep 17 '18 at 13:27
  • @JdeBP, here we're not calling setenv() but calling execve() directly. See also Protected environment variables? and When do environment strings not have "name=value" form? – Stéphane Chazelas Sep 17 '18 at 13:33
  • Not on FreeBSD and OpenBSD you are not. The env commands call setenv() and execvp(). – JdeBP Sep 17 '18 at 13:44
  • @JdeBP, but here, I'm talking of passing a "=foo" string in the envp in execve(), and printenv/env will happilly show it. I'm not talking of setenv(). – Stéphane Chazelas Sep 17 '18 at 13:47
  • No, you're talking about running env. It's what the question asks, and it's what you give in the answer, complete with an invocation of env with =foo. But your description of what goes on in that invocation, and indeed the output of the command that you show, are not universally true. – JdeBP Sep 18 '18 at 07:20
  • @JdeBP, OK I'm with you now. Yes, that example is to show what a particular shell does with env vars it can't map to variables. The env there is just the tool we used to pass that list. If your env implementation uses setenv() (which POSIX requires it rejects empty env vars, that's not just BSDs), you can use another method to pass that list of env strings, that's not the point I was making here, nor what the question is about. It's about what env/printenv print, not how they can be used to set / pass env vars. I hope the the edit addresses your objection. – Stéphane Chazelas Sep 18 '18 at 08:35
  • @StéphaneChazelas Thank you for your answer. The fact you pointed out, which was 'an assignment in ~/.bashrc doesn't make that variable an environment variable that would be passed into command executed by that shell process but rather instructs the interactive non-login shell to create and initialize that variable on startup' helps me a lot in confirming my understandings. I would have up voted all the answers in here if I could, however, due to my low reputation I could not. – Tran Triet Sep 18 '18 at 09:35
  • Also, can I have follow up questions on this, please? 1/ Does creating assignments in /etc/bashrc work the same (instructs shell to initialize variables on startups, not environment variable) 2/ How about setting an assignment in /etc/environment, does it create environment variable or initialize shell variables on startups? – Tran Triet Sep 18 '18 at 09:36
  • 1
    @TranTriet 1/ yes, bash does read /etc/bashrc or /etc/bash.bashrc depending on how it was built on startup when interactive and non-login. See info -n 'Bash Startup Files' bash for details. 2/ see edit. – Stéphane Chazelas Sep 18 '18 at 10:34
1

By doing:

USER_ENVI=10

You are setting a variable, but not an environment variable. For that you need:

export USER_ENVI=10

Or

USER_ENVI=10; export USER_ENVI

It is reasonable that this doesn't work for you:

$ env | grep SYSTEM_ENVI

There is no environment variable called SYSTEM_ENVI set. There is nothing about /etc/environment that we can say as you have not posted its contents (nor seem to be using it).

What env will print is exactly the same that printenv should print (without options).

Answers:

  1. Both env and printenv will report exactly the same list of variables actually set iif there are no options or variable list after the command.

  2. You could use set (without options) to see all variables set. That doesn't mean that you must use it. It depends on what you do want to list. It is perfectly valid and correct to list only the list of environment variables.

  • You've missed the fact that the question's assumption about /etc/environment is wrong. One several operating systems that's just a meaningless file in /etc that nothing uses. On those operating systems that do use it, it is important to know what reads it, and realize that it might be turned off. – JdeBP Sep 18 '18 at 07:38
  • Thanks for your answer. I did not mention in my post that an exported variable would show up in env's output. I know that it does. Now I understand that because export makes that shell variable an environment-variable and since env only prints the environment variables, that is the expected output. Again, I would have up voted your answer if I could but I can't due to my low reputation. Also, thanks for answering both questions! – Tran Triet Sep 18 '18 at 09:40
  • Since the above answer really gives me what I was looking for (e.g. 'creating an assignment in ~/.bashrc doesn't create an environment variable, but rather instructs the shell to create a shell variable on startup'), I am going to mark that answer as the official answer. But I want to thank you once again for your help! – Tran Triet Sep 18 '18 at 09:42
  • 1
    @JdeBP Thanks, your comment made me re-edit the answer but because /etc/environment wasn't used. It seems that you missed that fact. –  Sep 18 '18 at 14:41
  • 1
    @TranTriet I am happy that this answer has helped you. That is all that is needed. –  Sep 18 '18 at 14:45