Understanding *NIX Login Scripts

By Adam Bolte
We teamed up with SiteGround
To bring you up to 65% off web hosting, plus free access to the entire SitePoint Premium library (worth $99). Get SiteGround + SitePoint Premium Now

Have you ever faced a scenario where you needed to set an environment variable or run a program to alter your shell or desktop environment, but didn't know the best place to call it from?

This is a common situation. Many tasks require environment variables to function correctly, from running Debian packaging utilities to managing IaaS and everything in between.

Sometimes a program usually only needs to run once when you first log in, such as the xrandr command. Also, programs occasionally expect to be injected into the shell, such as rbenv, rvm or SitePoint's own envswitch utility.

These settings shouldn't just be loaded from anywhere. Sometimes there are a number of factors that should be considered before making a judgement about the best place.

Let's take a look at some common options present on a Debian GNU/Linux Jessie installation, and try to make sense of it all.


By default, Debian provides /etc/profile, which is immediately used to set the $PATH (used to declare command search paths).

if [ "`id -u`" -eq 0 ]; then
export PATH

For convenience, the root user (user ID 0) gets different paths defined to everyone else. That's because system binary (sbin) locations are ideally reserved for system administration or programs that must run as root. The games paths are omitted for root because you should never run programs as the root user unless absolutely necessary.

Next up, /etc/profile handles the setup of $PS1, which is used to set the primary prompt string. The default values defined are '$ ' (or '#' for root), unless the shell is Bash. If the shell is Bash, /etc/bash.bashrc is sourced to handle it (amongst other things) instead. We'll talk about /etc/bash.bashrc shortly.

So at this point, we can deduce that /etc/profile is read by all shells during login (i.e. by the login command). Instead of using the more efficient Bash built-in variable ${UID} to determine the user ID, /etc/profile calls the id command for this instead. Instead of defining a fancy shell prompt, a Bash-specific configuration was sourced, since Bash supports backslash-escaped special characters such as \u (username) and \h (hostname), which many other shells would not. /etc/profile should try to be POSIX compliant, so as to be compatible with any shell the user might install for herself.

Debian GNU/Linux often comes pre-installed with Dash, which is a basic shell that only aims to implement POSIX (and some Berkeley) extensions. If we modify /etc/profile (make a backup first!) to have the PS1='$ ' line set a different value and simulate a Dash login (via the dash -l command), we can see Dash uses the prompt we defined. However, if we instead call the dash command without the -l argument, /etc/profile is not read, and Dash falls back to a default value (which incidentally is what the original PS1 value was before we modified it).

The last interesting thing to note about /etc/profile is the following snippet at the end:

if [ -d /etc/profile.d ]; then
    for i in /etc/profile.d/*.sh; do
        if [ -r $i ]; then
            . $i
    unset i

In other words, anything readable matching the /etc/profile.d/*.sh glob is sourced. This is important, because it indicates that editing /etc/profile directly is never actually required (so restore that backup you made earlier!). Any variables defined above can be overridden in a separate file. One benefit of doing this is that it allows system upgrades to automatically add changes to /etc/profile, since Debian's Apt package management system typically won't touch modified configuration files.

~/.bash_profile, ~/.bash_login, and ~/.profile

One potential problem with /etc/profile is that it's located in a system-wide path. That means that changes there affect all users on the system. On a personal computer, that might not seem like much of a problem, but changes to it require root privileges. For these reasons, each individual Bash user account can create one of the files ~/.bash_profile, ~/.bash_login, or ~/.profile to be sourced — and the first file found (searched in the listed order) is used while any remaining files are ignored. Other shells — such as Dash — support something similar, but only look at ~/.profile. This allows the user to create a .bash_profile for Bash-specific situations, and if she sometimes switches to Dash or some other shell as her login shell (such as via the chsh -s dash command), ~/.profile can be reserved for that use-case.

One needs to keep the importance of this in mind. The default Debian skeleton directory (/etc/skel, used to house files and directories to be copied to new user accounts home directories) includes a .profile file, but not a .bash_profile or .bash_login file. Also, Debian uses Bash as the default user shell. Therefore, many users are accustomed to putting their Bash login shell settings in .profile.

I have seen installation instructions for projects such as RVM instruct the user to create a .bash_profile file, but this is dangerous, since it can break the user's shell environment! Even if the user did not modify .profile, she may be taking advantage of the default ~/.profile functionality that adds ~/bin to the $PATH environment variable. This will no longer work. One option which might improve safety is to add .bash_profile as a symlink to .bashrc in /etc/skel before creating user accounts.

If we look at Debian Jessie's default .profile script, we can see the following snippet:

# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"

This is similar to what we saw in /etc/profile, where /etc/bash.bashrc is sourced if found and the shell is Bash. The significance of this is discussed in the next section.

/etc/bash.bashrc and ~/.bashrc

When started, Bash will read both /etc/bash.bashrc and ~/.bashrc, in that order, but only if started as an interactive shell that isn't a login shell (which typically means when started via an xterm). This is standard behaviour for the Bash shell. However, Debian sources these files from the /etc/profile and ~/.profile login scripts respectively. This effectively alters the behaviour such that /etc/bash.bashrc and .bashrc (if they exist) are both always invoked when a Bash shell is started, regardless of being a login shell or not. Don't count on this behaviour being the same across different distributions.

.bashrc is a great place to add command aliases. In fact, some people have so many aliases that they prefer to keep them in a separate file. Debian's default .bashrc looks for ~/.bash_aliases and sources it if the file exists, so feel free to keep all of your Bash aliases there instead. .bashrc is also the best place for the user to override shell variables such as $PS1 or $HISTSIZE (the amount of command history to keep) if she wants. Debian's default .bashrc is 100+ lines long, but is quite straightforward reading and is well commented. As the name implies, .bashrc is not expected to be sourced by non-Bash shells.

~/.xsession and ~/.xsessionrc

If you're a GNU/Linux desktop user that logs in locally via a display manager (as opposed to the login program via a getty), /etc/profile and ~/.profile cannot be expected to work. Some display managers erroneously source these files directly — such as the Gnome Display Manager — but other DMs, such as LightDM, do not. Fortunately, you have other options.

When an X Window System session is started (regardless of using a display manager or startx from a virtual terminal) the /etc/X11/Xsession shell script will be executed. This is basically the equivalent to /etc/profile used by login shells, only for X and not sourced but directly executed. It is also considerably more complex. Similar to how /etc/profile reads in scripts from /etc/profile.d, /etc/X11/Xsession sources scripts under /etc/X11/Xsession.d. All scripts in this directory start with a number, so scripts will be loaded in the numbered order.

Debian Jessie includes a file there named 40x11-common_xsessionrc. All it does is check to see if ~/.xsessionrc is readable, and (if so) sources it. This makes ~/.xsessionrc the perfect place to load environment variables or run once-off utilities at launch (such as xrandr or xmodmap) that only apply to X sessions. You could also use this to source /etc/profile and ~/.profile if you wanted, so any environment variables specified there will be inherited by your session manager as well (if they weren't already). Note that .xsessionrc does not exist by default, so you must create it.

If we continue browsing the files in /etc/X11/Xsession, we find 50x11-common_determine-startup which determines the session manager to
load. If the ~/.xsession file exists and is executable, it will be saved and executed later as part of 99x11-common_start. Since ~/.xsession is meant for running the session manager, the X session will log out and you will be returned to your display manager login screen when this script terminates.

Like ~/.xsessionrc, ~/.xsession does not exist by default, so you have to create one if you want it. You might create a simple .xsession script that looks as follows:

# Start our session manager of choice.
exec x-session-manager

where x-session-manager defaults to whatever is configured via the update-alternatives command. This way, you can easily change the session manager away from the system-wide default, just by replacing x-session-manager with say, /usr/bin/startxfce4 (to switch to XFCE) and other user accounts will be left completely unaffected.

Of course, many display managers provide the ability to select common session managers directly from the login screen, so this file is often not necessary. However .xsession provides a lot of flexibility, and you could have any program called here — not just session managers. For example, you might call chromium or iceweasel in a while loop here instead to implement a basic kiosk-mode setup.


We covered earlier the files that are read when a user runs an interactive Bash login shell, but what if you wanted to run a program when you log out? For that use-case, ~/.bash_logout is your friend. The default included in Debian is only used to clear the screen (which I think is important from a security perspective), but with a bit of imagination could be used for other purposes — for example, to display a reminder for a few seconds before you walk away from your machine.

The main limiting factor is that .bash_logout is only read when logging out of an interactive shell, and one cannot assume it will be loaded when logging out of an X session.

Other options

That about covers the most common options available to you. Other options may exist, depending on your installation (such as /etc/environment), but I don't consider them as likely to exist on other platforms, and have rarely had the need to touch those.


So where should you place your system-wide environment variables? If you want an environment variable to affect every user, /etc/profile.d/somefile.sh is a good bet. However, this assumes you're using a login manager that sources /etc/profile. If not, you could (as an administrator) add a script to /etc/X11/Xsession.d/ to source /etc/profile instead.

If you want a script to find a personal directory location and add it to your PATH, you need to consider if the directory will move around a lot. If you add code to do that to .profile, the user will need to log out and in again for the PATH to reflect a directory change during the user session. If you instead added the code to .bashrc, it means the code will be executed every single time the user opens an xterm — which is probably not ideal if it takes more than half a second or so to execute. So it's a matter of weighing up the trade-offs.

What if you want an environment variable only for your personal login sessions? If it only concerns X Sessions, you could add it to ~/.xsessionrc. This has the advantage that it will typically be available to all programs launched through X session manager, since it is set prior to launching the X session manager, and hence is inherited. For example, some graphics drivers can have vsync disabled by running

export vblank_mode=0

So placing that in .xsessionrc should affect all programs.

However if that line was added to .bashrc, only programs launched via the xterm would be affected; programs launched via a window manager launcher would run as normal. You could add it to .profile and source .profile from .xsessionrc, but then you needlessly export the environment variable even when your X server is not running.

Hopefully you now have a better understanding how login and logout scripts work on Debian GNU/Linux systems. Let us know in the comments if you've created or encountered any particularly interesting or creative uses for these login and logout scripts, and how you went about it.

Next in this series, I'll be discussing dotfile management options.

The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
We teamed up with SiteGround
To bring you up to 65% off web hosting, plus free access to the entire SitePoint Premium library (worth $99). Get SiteGround + SitePoint Premium Now
Login or Create Account to Comment
Login Create Account