Difference between `su` and `su -` in Bash on Linux, or Login Shell and Interactive Shell

Although this topic has been discussed from B.C., I want to re-summarize the difference between su and su - while explaining the meaning of login shell and interactive shell.

What is Login Shell and Interactive Shell?

A login shell is one whose first character of argument zero is ‘-’, or one invoked with the --login option.

An interactive shell is one started without non-option arguments, unless -s is specified, without specifying the -c option, and whose input and output are both connected to terminals (as determined by isatty(3)), or one started with the -i option.1

When we have normally logged in to a Linux system, the shell where we are in is called login (and also, interactive) shell. We can confirm this by issuing the below command:

$ echo $0
-bash # <- the first character of the output is '-'

We can go into non-login, interactive shell by simply type bash after logging in as above.

$ echo $0
-bash # <- we are in login shell
$ printenv SHLVL
1
$
$ bash
$ # <- it looks nothing has happened...
$
$ echo $0
bash # <- but we are now in non-login, interactive shell
$ printenv SHLVL
2

OK. Then, what is the difference between the two? We can interact with them apparently without any difference at all.

The difference becomes clear when you consider the initializing process of bash. Haven't you been in trouble determining where to put a line of export PATH="/path/to/your/bin:$PATH"? Some document says ~/.bash_profile and the other says ~/.profile, ~/.bashrc, or something like them. In fact, it depends on your situation and the criterion is below:2

  • When launched, login shell firstly searches for /etc/profile and loads it if it exists. After that, the shell searches for the below files in that order. When it runs into one, it loads that one and stops searching. At most only one of the below is loaded.
    1. ~/.bash_profile
    2. ~/.bash_login
    3. ~/.profile
  • When launched, interactive shell searches for ~/.bashrc. If it exists, the shell loads it.

So, it is better to put environment variable declarations on ~/.bash_profile or one of its siblings. ~/.bashrc is a good place to put shell options, functions, or aliases. Since ~/.bashrc is NOT automatically loaded by login shell, many Linux distribution has default settings that load ~/.bashrc inside /etc/profile, ~/.bash_profile, or so. As an example, Ubuntu's ~/.profile is below:

# ~/.profile: executed by the command interpreter for login shells.
# This file is not read by bash(1), if ~/.bash_profile or ~/.bash_login
# exists.
# see /usr/share/doc/bash/examples/startup-files for examples.
# the files are located in the bash-doc package.

# the default umask is set in /etc/profile; for setting the umask
# for ssh logins, install and configure the libpam-umask package.
#umask 022

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

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

In the above script loads $HOME/.bashrc if it exists.

What is the Difference between su and su -?

Now that we understand login shell and interactive shell, we can grasp the meaning of su and su -.

su means substitute user and we can switch to another user after we have logged in. Assume we are user alice and we want to switch to user bob, we can do that as follows:

alice@host:~$ su bob
Password: *** # <- input Bob's password
bob@host:/home/alice$ # <- we have switched to Bob, but we are still in Alice's home directory

The problem here, however, is that the user is still in Alice's home directory and doesn't have the environment set so that the shell looks as if Bob directly logged in to the system.

In a practical world, Bob would have his specific environment variables. For example, assume Bob has installed tj/n to manage multiple versions of Node.js and has an environment variable $N_PREFIX set to /home/bob/n and has $N_PREFIX/bin at the beginning of $PATH in ~/.profile like below.

# /home/bob/.profile

# ...some other settings...

# Initialize tj/n
export N_PREFIX="$HOME/n"
export PATH="$N_PREFIX/bin:$PATH"

When Alice switches to Bob like the previous example, she would expect to have node installed with tj/n in $PATH. In this case, however, she doesn't have it and cannot smoothly use node like Bob would do.

alice@host:~$ printenv N_PREFIX # <- $N_PREFIX not set
alice@host:~$ which n >/dev/null 2>&1 && which node >/dev/null 2>&1 && node -v || echo "node unavailable"
node unavailable
alice@host:~$ su bob
Password: ***
bob@host:/home/alice$ printenv N_PREFIX # <- $N_PREFIX still not set
bob@host:/home/alice$ which n >/dev/null 2>&1 && which node >/dev/null 2>&1 && node -v || echo "node unavailable"
node unavailable

In order to solve this problem, we can use su's - option. With this option, we can spawn a brand new bash as a login shell when we switch to another user as if we logged in to the system as the target user.

alice@host:~$ printenv N_PREFIX # <- $N_PREFIX not set
alice@host:~$ which n >/dev/null 2>&1 && which node >/dev/null 2>&1 && node -v || echo "node unavailable"
node unavailable
alice@host:~$ su - bob
Password: ***
bob@host:~$ # <- unlike the above example, we are immediately in Bob's home directory
bob@host:~$ printenv N_PREFIX
/home/bob/n # <- $N_PREFIX is set
bob@host:~$ which n >/dev/null 2>&1 && which node >/dev/null 2>&1 && node -v || echo "node unavailable"
v14.15.5 # <- now, node is available

For this reason, using - option is safer and more natural. It is generally recommended to use - option with su.

When we issue su or su - without target username, we can switch to the root user.

$ su -
Password: *** # <- input root user's password
# # <- switched to root user

In the above example, we type in root user's password. In some Linux distributions including Ubuntu, however, root user doesn't have password set by default. Still, in that case, we can switch to the root user if our user is a sudoer, who can invoke sudo.

$ sudo su -
Password: *** # <- input your password (depending on setting, you may not have to input at all)
# # <- switched to root user

In conclusion, we can issue sudo su - to switch to the root user without having to set root user's password while spawning root user's environment naturally.