7.9. Environment Variables
As we mentioned earlier, the environment strings are usually of the form
name=value
The UNIX kernel never looks at these strings; their interpretation is up to the various applications. The shells, for example, use numerous environment variables. Some, such as HOME and USER, are set automatically at login, and others are for us to set. We normally set environment variables in a shell start-up file to control the shell's actions. If we set the environment variable MAILPATH, for example, it tells the Bourne shell, GNU Bourne-again shell, and Korn shell where to look for mail.
ISO C defines a function that we can use to fetch values from the environment, but this standard says that the contents of the environment are implementation defined.
#include <stdlib.h>
char *getenv(const char *name);
| Returns: pointer to value associated with name, NULL if not found |
Note that this function returns a pointer to the value of a name=value string. We should always use getenv to fetch a specific value from the environment, instead of accessing environ directly.
Some environment variables are defined by POSIX.1 in the Single UNIX Specification, whereas others are defined only if the XSI extensions are supported. Figure 7.7 lists the environment variables defined by the Single UNIX Specification and also notes which implementations support the variables. Any environment variable defined by POSIX.1 is marked with •; otherwise, it is an XSI extension. Many additional implementation-dependent environment variables are used in the four implementations described in this book. Note that ISO C doesn't define any environment variables.
Figure 7.7. Environment variables defined in the Single UNIX SpecificationVariable | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 | Description |
---|
COLUMNS | • | • | • | • | • | terminal width | DATEMSK | XSI | | • | | • | getdate(3) template file pathname | HOME | • | • | • | • | • | home directory | LANG | • | • | • | • | • | name of locale | LC_ALL | • | • | • | • | • | name of locale | LC_COLLATE | • | • | • | • | • | name of locale for collation | LC_CTYPE | • | • | • | • | • | name of locale for character classification | LC_MESSAGES | • | • | • | • | • | name of locale for messages | LC_MONETARY | • | • | • | • | • | name of locale for monetary editing | LC_NUMERIC | • | • | • | • | • | name of locale for numeric editing | LC_TIME | • | • | • | • | • | name of locale for date/time formatting | LINES | • | • | • | • | • | terminal height | LOGNAME | • | • | • | • | • | login name | MSGVERB | XSI | • | | | • | fmtmsg(3) message components to process | NLSPATH | XSI | • | • | • | • | sequence of templates for message catalogs | PATH | • | • | • | • | • | list of path prefixes to search for executable file | PWD | • | • | • | • | • | absolute pathname of current working directory | SHELL | • | • | • | • | • | name of user's preferred shell | TERM | • | • | • | • | • | terminal type | TMPDIR | • | • | • | • | • | pathname of directory for creating temporary files | TZ | • | • | • | • | • | time zone information |
In addition to fetching the value of an environment variable, sometimes we may want to set an environment variable. We may want to change the value of an existing variable or add a new variable to the environment. (In the next chapter, we'll see that we can affect the environment of only the current process and any child processes that we invoke. We cannot affect the environment of the parent process, which is often a shell. Nevertheless, it is still useful to be able to modify the environment list.) Unfortunately, not all systems support this capability. Figure 7.8 shows the functions that are supported by the various standards and implementations.
Figure 7.8. Support for various environment list functionsFunction | ISO C | POSIX.1 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
---|
getenv | • | • | • | • | • | • | putenv | | XSI | • | • | • | • | setenv | | • | • | • | • | | unsetenv | | • | • | • | • | | clearenv | | | | • | | |
clearenv is not part of the Single UNIX Specification. It is used to remove all entries from the environment list.
The prototypes for the middle three functions listed in Figure 7.8 are
#include <stdlib.h>
int putenv(char *str);
int setenv(const char *name, const char *value,
int rewrite);
int unsetenv(const char *name);
| All return: 0 if OK, nonzero on error |
The operation of these three functions is as follows.
The putenv function takes a string of the form name=value and places it in the environment list. If name already exists, its old definition is first removed. The setenv function sets name to value. If name already exists in the environment, then (a) if rewrite is nonzero, the existing definition for name is first removed; (b) if rewrite is 0, an existing definition for name is not removed, name is not set to the new value, and no error occurs. The unsetenv function removes any definition of name. It is not an error if such a definition does not exist. Note the difference between putenv and setenv. Whereas setenv must allocate memory to create the name=value string from its arguments, putenv is free to place the string passed to it directly into the environment. Indeed, on Linux and Solaris, the putenv implementation places the address of the string we pass to it directly into the environment list. In this case, it would be an error to pass it a string allocated on the stack, since the memory would be reused after we return from the current function.
It is interesting to examine how these functions must operate when modifying the environment list. Recall Figure 7.6: the environment listthe array of pointers to the actual name=value stringsand the environment strings are typically stored at the top of a process's memory space, above the stack. Deleting a string is simple; we simply find the pointer in the environment list and move all subsequent pointers down one. But adding a string or modifying an existing string is more difficult. The space at the top of the stack cannot be expanded, because it is often at the top of the address space of the process and so can't expand upward; it can't be expanded downward, because all the stack frames below it can't be moved.
If we're modifying an existing name: If the size of the new value is less than or equal to the size of the existing value, we can just copy the new string over the old string. If the size of the new value is larger than the old one, however, we must malloc to obtain room for the new string, copy the new string to this area, and then replace the old pointer in the environment list for name with the pointer to this allocated area.
If we're adding a new name, it's more complicated. First, we have to call malloc to allocate room for the name=value string and copy the string to this area. Then, if it's the first time we've added a new name, we have to call malloc to obtain room for a new list of pointers. We copy the old environment list to this new area and store a pointer to the name=value string at the end of this list of pointers. We also store a null pointer at the end of this list, of course. Finally, we set environ to point to this new list of pointers. Note from Figure 7.6 that if the original environment list was contained above the top of the stack, as is common, then we have moved this list of pointers to the heap. But most of the pointers in this list still point to name=value strings above the top of the stack. If this isn't the first time we've added new strings to the environment list, then we know that we've already allocated room for the list on the heap, so we just call realloc to allocate room for one more pointer. The pointer to the new name=value string is stored at the end of the list (on top of the previous null pointer), followed by a null pointer.
|