Introduction
I have been fiddling with Docker recently in an effort to building a containerised data science development environment both for work and personal use. A lot of times I found myself needing to tweak environment variables in Linux, especially with the system’s PATH
variable. In particular, I need to step down from root to a non-privileged user before starting a long-running service during container start-up; since the service’s binary resides in a conda environment, I need to make sure conda is properly set up when switching to a new user. I had only some vague understanding back then that most of these environment variables are set up by the various start-up scripts sourced during user login, but I didn’t know about the details; so I took the opportunity to read through the internet about the initialisation process of Linux shells.
The Start-up Scripts
Linux sources a number of start-up scripts when a shell is opened. Some examples include:
- System-wide:
/etc/profile
;/etc/profile.d/*.sh
: readablesh
scripts in/etc/profile.d/
will be sourced as part of/etc/profile
;/etc/bash.bashrc
on some distributions (e.g. Ubuntu);/etc/bashrc
on some other systems;/etc/environment
, arguably not a start-up script per se, but its contents are used to set environment variables;
- Per-user:
~/.profile
;~/.bash_profile
;~/.bash_login
;~/.bashrc
;
Although, as always, not all of them are present in all Linux distributions.
When a user starts a shell, some or all of the above scripts may be sourced; the specific scripts sourced depends on two factors:
- Whether the shell is a login shell or a non-login shell, and;
- Whether the shell is an interactive shell or a non-interactive shell.
So let’s look at login shells and interactive shells respectively.
Login Shell vs. Non-login Shell
A login shell, quite self-explanatorily, is a shell that is started for a user when he or she tries to login to the system. A login shell will typically authenticate the user by asking for his or her username and password.
Once a user is logged into the system, additional shells started by the user (by issuing sh
, bash
, or zsh
via a terminal, for example) are typically non-login shells. In many cases, a user can force opening a login shell after logging in by appending a dash (-
) as an option to the shell command (e.g. bash -l
) or by explicitly enabling the -l
/--login
option when issuing the shell command.
For a GUI desktop environment, the desktop environment that logs you into the system is typically the login shell; subsequent terminal emulators usually operate as non-login shells.
Interactive Shell vs. Non-interactive Shell
See that blinking prompt at the console? A shell that awaits the user’s command in a loop is typically an interactive shell; this is the most common type of shell a user would encounter.
A non-interactive shell is usually used to execute a shell script. Commands such as bash some_script.sh
or bash -c 'echo "This is sourced by a non-interactive shell"'
will operate in non-interactive shells.
Some Examples Ways to Start Different Types of Shells:
- Interactive login shell:
bash -l
: start an interactive login shell usingbash
as the current user;bash -l -i
: same as above, only explicitly indicate an interactive shell with-i
;su -l user
: starts an interactive login shell asuser
;su - user
: same as above.
- Interactive non-login shell:
bash
: start an interactive non-login shell usingbash
as the current user;bash -i
: same as above;su user
: starts an interactive non-login shell asuser
.
- Non-interactive login shell:
bash -l -c command
: starts a non-interactive login shell as the current user and executecommand
;sudo -i -u user command
: starts a non-interactive login shell asuser
and executescommand
;su -l -c command
: same as above.
- Non-interactive non-login shell:
bash -c command
: executecommand
in a non-interactive non-login shell as the current user;sudo -u user command
: starts a non-interactive non-login shell asuser
and executescommand
;su -c command user
: same as above.
For the su
and sudo
commands, the default shell for user
as specified in /etc/passwd
will be invoked. To use a different shell with the above commands, one should also supply the path of the desired shell with the -s
or --shell
option, e.g.: su -l -c 'echo $(whoami)' -s /bin/sh someuser
shows the user name of someuser
in a login sh
shell.
Which Scripts for Which Shell?
Now that we have got the different types of shells are out of way, we may proceed to find out the rules for determining which type of shell sources which scripts. In general:
- When a user invokes a login shell, the following scripts are sourced:
/etc/profile
, which in itself invokes the scripts in/etc/profile.d
. Note that only scripts readable by the logging-in user will be sourced — if a start-up script is placed in/etc/profile.d
byroot
it will not be readable by ordinary users by default; one shouldchmod +r
the script after placement.- The first available script among the following:
~/.bash_profile
(bash only, mostly present on Mac OS X),~/.bash_login
(bash only),~/.profile
(all shells), in that order.
- When a user invokes an interactive shell, the following scripts are sourced:
/etc/bash.bashrc
, which sets up system-wide stuff that are only relevant in an interactive context, e.g. console prompt, terminal type (xterm, colour), bash completion, etc.~/.bashrc
, a per-user version of/etc/bash.bashrc
, also sets up aliases by sourcing~/.bash_aliases
.
So… Where Should My Environment Variables Go?
In general, I find it a good practice to place environment variable set-ups in the start-up scripts for the login shells, namely /etc/profile
, /etc/profile.d/*.sh
, and ~/.profile
. This is perhaps especially true for accumulative environment variables such as PATH
; updating such variables in start-up scripts sourced for interactive shells may lead to duplicated segments when interactive shells are invoked in a nested manner.
Instead of editing the PATH
variable all in /etc/profile
, it can be beneficial to modularise the updates to the environment variable and place them in separate scripts in /etc/profile.d/
, one for each application. For example, the Manjaro Linux distribution uses this strategy to set up environment variables for Java and Perl. It also makes it easier to control which variable setting applies to which user by setting up the read permissions on these scripts accordingly.