The Unix shell offers the ability to set, modify, or unset environment variables, and to mark whether or not environment variables are exported to subprocesses. Complications arise depending on whether the subprocess is another shell or some other executable, and if a shell, whether that shell process is a login shell or interactive. Other concerns arise should the executable change the effective user—sudo(8), for example.
The following examples assume a Bourne shell.
Unexported Environment Variables
Environment variables that have not been exported are unavailable to subprocesses. This can cause unexpected behavior, especially if someone forgets to export the environment variable, or if the shell does not read the “run command” (rc) file where the export is made. Debugging requires determining whether or not the variable is exported, and whether the problem is due to an environment variable setting, or something else. These issues are usually complicated by complex shell configuration spread across any number of different global and user shell configuration files.
Consider a custom location for Unix man(1) pages. These custom locations are usually handled by setting appropriate values in the MANPATH environment variable:
$ cd /var/tmp
$ ls share/man/man1/
bar.1 foo.1
$ man foo
No manual entry for foo
$ man bar
No manual entry for bar
A naive setting of MANPATH will lack an export, though this will make the shell show that MANPATH has been set:
$ echo $MANPATH
$ cat manrc
MANPATH=/var/tmp/share/man
$ . /var/tmp/manrc
$ echo $MANPATH
/var/tmp/share/man
$ man foo
No manual entry for foo
MANPATH has not been marked for export, so is not available to subprocesses, such as perl:
$ echo $MANPATH
/var/tmp/share/man
$ perl -le 'print $ENV{MANPATH}'
$
Once a variable is marked for export (this need only be done once per shell process; exporting a environment variable multiple times is like voting for the same candidate twice on a single ballot), subprocesses will obtain the setting:
$ export MANPATH
$ perl -E 'say $ENV{MANPATH}'
/var/tmp/share/man
$ man foo
…
Programs usually offer multiple ways to set configuration values; man(1) offers a -M argument, which performs the same role as the MANPATH environment variable, while ruby offers both RUBYLIB and a -I argument to set a custom library path. These can be used to test whether the problem is with the environment variable, or something else:
$ man bar
No manual entry for bar
$ ls share/man/man1/bar.1
share/man/man1/bar.1
$ echo $MANPATH
/var/tmp/share/man
$ perl -E 'say $ENV{MANPATH}'
/var/tmp/share/man
$ man -M /var/tmp/share/man bar
No manual entry for bar
The bar.1 man page does exist, and the problem is not due to MANPATH, as the equivalent -M option also fails. The actual problem here is a permissions problem, and a highly misleading message from the instance of man involved:
$ ls -l share/man/man1/*1
---------- 1 jmates jmates 6747 Jun 28 21:38 share/man/man1/bar.1
-r--r--r-- 1 jmates jmates 6747 Jun 28 21:38 share/man/man1/foo.1
$ chmod +r share/man/man1/*1
$ ls -l share/man/man1/*1
-r--r--r-- 1 jmates jmates 6747 Jun 28 21:38 share/man/man1/bar.1
-r--r--r-- 1 jmates jmates 6747 Jun 28 21:38 share/man/man1/foo.1
$ man bar
…
Other instances of man, for example on OpenBSD, instead print an informative message:
$ man bar
man: Formatting manual page...
troff: fatal error: can’t open ‘…’: Permission denied
Vague and misleading error messages are unfortunately common in computing (disclaimer: I am the author of Acme::EdError). Develop methods to narrow down where a suspected problem is, and fully learn all the configuration options of the shell or software involved.
ZSH offers inspection of environment variables, which can help avoid needling to know perl or writing some other program outside the shell to show environment variables:
$ echo ${(t)MANPATH}
scalar-export-unique-special
$ echo ${(t)PAGER}
scalar-export
Shells also may offer options to disable parsing of the shell configuration (zsh -f), though the environment of the parent process may need to be sanitized somehow. Also consider testing under a new user account that has no custom shell configuration (though this will pickup any global run command definitions), or on a clean virtual instance. Note that some operating systems or programs offer other means of setting environment variables, for example ~/.MacOSX/environment.plist on Mac OS X.
Changing the Effective User
Concerns:
- Consider sudo -H … as this will help avoid various user environment settings—PS1, TZ, and many others—from polluting the environment of the new user. TZ in particular can cause problems should a process need to run as UTC, while the user instead sets some random local timezone. See also set home and always set home in sudoers(5). Daemon startup scripts should perhaps sanitize the environment, or set appropriate defaults, rather than passing through whatever has been exported from who knows where.
- PS1 should not be exported: it is an interactive shell environment variable, is useless to every other executable, and should be specially set for the root or any other role accounts for the rare occasions someone needs to operate as such a user. Note that HIPAA, SOX, and other regulations frown on the use of role accounts, as there may be no record of who made some change if only a role account login is recorded.
Digression: effective group changes also have subtleties. System V permissions (Solaris, Linux) use the effective group of the process when creating new directories. Therefore, changing the effective group of a process may cause directories to be created with unexpected permissions. On Linux, the *BSD semantics of inheriting the permissions from the parent directory can be enabled via a special filesystem mount option, or by setting the group sticky bit. Hence modefix -d g+s somedir via modefix, or using configuration management to ensure that directory trees have appropriate permissions.