Environment Variables
Each process also has access to a list of environment variables. The environment variables, like the command-line values, are stored as a ragged array of characters . Environment variables, which are most commonly set at the shell level, [11] are passed to a process by its parent when the process begins execution. Environment variables can be accessed in a program by using an external pointer called environ , which is defined as
[11] If at the command-line level you enter the shell command env (or printenv ), the system should display a list of environment variables and their contents.
extern char **environ;
In most older (and in some current) versions of Linux, the environment variables could also be accessed by using a third argument in the function main called envp . When used, the envp argument to main is defined as
main(int argc,char *argv[],char **envp /* OR as *envp[]*/)
As environ and envp can both be used to accomplish the same thing, and current standards discourage the use of envp , only the use of the external pointer environ will be discussed in detail.
The contents of the environment variables can be obtained in a manner similar to the command-line arguments (Program 2.7).
A partial listing of the output of this program run on a local system is show in Figure 2.20.
Program 2.7 Displaying environment variables.
File : p2.7.cxx /* Using the environ pointer to display the command line */ #include + using namespace std; extern char **environ; int main( ){ for ( ; *environ ; ) 10 cout << *environ++ << endl; return 0; }
Figure 2.20 Output of Program 2.7.
linux$ p2.7 PWD=/home/faculty/gray/revision/02 VENDOR=intel REMOTEHOST=zeus.cs.hartford.edu HOSTNAME=kahuna LOGNAME=gray SHLVL=2 GROUP=faculty USER=gray PATH=/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:. . . .
The output shows that all environment variables are stored as strings in the format name =value. Many of the environment variables shown here are common to all Linux systems (e.g., USER , PATH , etc.), while others are system-dependent (e.g., VENDOR ). Note that by convention environment variables are normally spelled in uppercase. For the more curious , the manual page on environ ( $ man 5 environ ) furnishes a detailed description of the commonly found environment variables and their uses.
The two library calls shown in Tables 2.23 and 2.24 can be used to manipulate environment variables.
The first library call, getenv , searches the environment list for the first occurrence of a specified variable. The character string argument passed to getenv should be of the format name , where name is the name of the environment variable to find without an appended =. Note that name is case-sensitive (environment variables are often in uppercase). If getenv is successful, it returns a pointer to the string assigned to the environment variable specified; otherwise , it returns a NULL pointer. If getenv fails, it returns a -1 and sets errno to ENOMEM (12"Cannot allocate memory"). In Program 2.8 the output (shown in Figure 2.21) indicates that in this case the environment variable TERM has been found and that its current value is vt220 . Notice that only the string to the right of the equals was returned by getenv .
Table 2.23. Summary of the getenv Library Function.
Include File(s) |
Manual Section |
3 |
||
Summary |
char *getenv( const char *name ); |
|||
Return |
Success |
Failure |
Sets errno |
|
Pointer to the value in the environment |
NULL |
Table 2.24. Summary of the putenv Library Function.
Include File(s) |
Manual Section |
3 |
||
Summary |
Int putenv( const char *name ); |
|||
Return |
Success |
Failure |
Sets errno |
|
-1 |
Yes |
Program 2.8 Using getenv .
File : p2.8.cxx /* Displaying the contents of the TERM variable */ #include + #include using namespace std; int main( ){ char *c_ptr; 10 c_ptr = getenv("TERM"); cout << "The variable TERM is " << (c_ptr==NULL ? "NOT found" : c_ptr) << endl; return 0; + }
Figure 2.21 Checking the output of Program 2.8.
linux$ echo $TERM vt220 linux$ p2.8 The variable TERM is vt220
Modifying or adding environment variable information, which is usually accomplished with the library function putenv , is a little trickier. The environment variables, along with the command-line values, are stored by the system in the area just beyond the stack segment for the process (see Chapter 1, Section 1.8). This area is accessible by the process and can be modified by the process, but it cannot be expanded. When environment variables are added or an existing environment variable is modified so it is larger (storage-wise) than its initial setting, the system will move the environment variable information from its stack location to the text segment of the process (the putenv function uses malloc to allocate additional space). To further complicate the issue in this situation, envp (if supported) will still point to the table on the stack when referencing the original environment variables, but will point to the text segment for the new environment variable. This is yet another reason to stay clear of envp !
One last caveat appears in the putenv manual page. The argument for putenv should not be an automatic variable (such as a variable local to a function), as these variables become undefined once the function in question is exited.
Program 2.9 demonstrates the putenv function.
Program 2.9 Using putenv .
File : p2.9.cxx /* Using putenv to modify the environment as seen by parent child */ #define _GNU_SOURCE + #include #include #include #include using namespace std; 10 extern char **environ; int show_env( char ** ); int main( ){ int numb; + cout << "Parent before any additions **********" << endl; show_env( environ ); putenv("PARENT_ED=parent"); cout << "Parent after one addition **********" << endl; show_env( environ ); 20 if ( fork( ) == 0 ){ // In the CHILD now cout << "Child before any additions *********" << endl; show_env( environ ); putenv("CHILD_ED=child"); cout << "Child after one addition *********" << endl; + show_env( environ ); return 0; } // In the PARENT now sleep( 10 ); // Make sure child is done cout << "Parent after child is done **********" << endl; 30 numb = show_env( environ ); cout << "... and at address [" << hex << environ+numb << "] is ... " << (*(environ+numb) == NULL ? "Nothing!" : *(environ+numb)) << endl; + return 0; } /* Display the contents of the passed list ... return number found */ 40 int show_env( char **cp ){ int i; for (i=0; *cp; ++cp, ++i) cout << "[" << hex << cp << "] " << *cp << endl; return i; + }
The abridged output (some of the intervening lines of output were removed for clarity) of this program, when run on a local system, is explained in Figure 2.22.
Figure 2.22 Output of Program 2.9.
linux$ p2.9 Parent before any additions ********** [0xbffffc9c] TERM=vt220 <-- 1 . . . [0xbffffd08] CA_DB= Parent after one addition ********** [0x8049ec8] TERM=vt220 . . . [0x8049f34] CA_DB= [0x8049f38] PARENT_ED=parent <-- 2 Child before any additions ********** [0x8049ec8] TERM=vt220 <-- 3 . . . [0x8049f34] CA_DB= [0x8049f38] PARENT_ED=parent <-- 3 Child after one addition ********** [0x8049ec8] TERM=vt220 . . . [0x8049f34] CA_DB= [0x8049f38] PARENT_ED=parent [0x8049f3c] CHILD_ED=child <-- 4 Parent after child is done ********** [0x8049ec8] TERM=vt220 . . . [0x8049f34] CA_DB= [0x8049f38] PARENT_ED=parent <-- 5 ... and at address [0x8049f3c] is ... Nothing!
(1) The environment variables start their life in storage just beyond the stack segment (notice the addresses).
(2) This environment variable is added by the parent process. All variables have been moved to the text segment.
(3) Notice the addresses in the child are the same.
(4) This environment variable is added by the child process.
(5) When the child process is gone, so is the environment variable it added.
There are several important concepts that can be gained by examining this program and its output. First, it is clear that the addresses associated with the environment variables are changed (from the stack segment to the text segment) when a new environment variable is added. Second, the child process inherits a copy of the environment variables from its parent. Third, as each process has its own address space, it is not possible to pass information back to a parent process from a child process. [12] Fourth, when adding an environment variable, the name= value format should be adhered to. While it is not checked in the example program, putenv will return a 0 if it is successful and a -1 if it fails to accomplish its mission.
[12] I am sure that many human children would say this is also true for their parent/child relationshipeverything ( especially tasks ) seems to flow one way.