I understand the basic difference between an interactive shell and a non-interactive shell. But what exactly differentiates a login shell from a non-login shell?
Can you give examples for uses of a non-login interactive shell?
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems. It only takes a minute to sign up.Sign up to join this community
A login shell is the first process that executes under your user ID when you log in for an interactive session. The login process tells the shell to behave as a login shell with a convention: passing argument 0, which is normally the name of the shell executable, with a
- character prepended (e.g.
-bash whereas it would normally be
bash. Login shells typically read a file that does things like setting environment variables:
~/.profile for the traditional Bourne shell,
~/.bash_profile additionally for bash†,
~/.zprofile for zsh†,
~/.login for csh, etc.
When you log in on a text console, or through SSH, or with
su -, you get an interactive login shell. When you log in in graphical mode (on an X display manager), you don't get a login shell, instead you get a session manager or a window manager.
It's rare to run a non-interactive login shell, but some X settings do that when you log in with a display manager, so as to arrange to read the profile files. Other settings (this depends on the distribution and on the display manager) read
~/.profile explicitly, or don't read them. Another way to get a non-interactive login shell is to log in remotely with a command passed through standard input which is not a terminal, e.g.
ssh example.com <my-script-which-is-stored-locally (as opposed to
ssh example.com my-script-which-is-on-the-remote-machine, which runs a non-interactive, non-login shell).
When you start a shell in a terminal in an existing session (screen, X terminal, Emacs terminal buffer, a shell inside another, etc.), you get an interactive, non-login shell. That shell might read a shell configuration file (
~/.bashrc for bash invoked as
~/.zshrc for zsh,
~/.cshrc for csh, the file indicated by the
ENV variable for POSIX/XSI-compliant shells such as dash, ksh, and bash when invoked as
$ENV if set and
~/.mkshrc for mksh, etc.).
When a shell runs a script or a command passed on its command line, it's a non-interactive, non-login shell. Such shells run all the time: it's very common that when a program calls another program, it really runs a tiny script in a shell to invoke that other program. Some shells read a startup file in this case (bash runs the file indicated by the
BASH_ENV variable, zsh runs
~/.zshenv), but this is risky: the shell can be invoked in all sorts of contexts, and there's hardly anything you can do that might not break something.
† I'm simplifying a little, see the manual for the gory details.
To tell if you are in a login shell:
prompt> echo $0 -bash # "-" is the first character. Therefore, this is a login shell. prompt> echo $0 bash # "-" is NOT the first character. This is NOT a login shell.
In Bash, you can also use
prompt> shopt login_shell login_shell off
on in a login shell).
Information can be found in
man bash (search for Invocation). Here is an excerpt:
A login shell is one whose first character of argument zero is a -, or one started with the --login option.
You can test this yourself. Anytime you SSH, you are using a login shell. For Example:
prompt> ssh user@localhost user@localhost's password: prompt> echo $0 -bash
The importance of using a login shell is that any settings in
/home/user/.bash_profile will get executed. Here is a little more information if you are interested (from
"When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file /etc/profile, if that file exists. After reading that file, it looks for
~/.profile, in that order, and reads and executes commands from the first one that exists and is readable. The --noprofile option may be used when the shell is started to inhibit this behavior."
I'll elaborate on the great answer by Gilles, combined with Timothy's method for checking login shell type.
If you like to see things for yourself, try the snippets and scenarios bellow.
Checking whether shell is (non-)interactive
if tty -s; then echo 'This is interactive shell.'; else echo 'This is non-interactive shell.'; fi
Checking whether shell is (non-)login
If output of
echo $0 starts with
-, it's login shell (
echo $0 output example:
-bash). Otherwise it's non-login shell (
echo $0 output example:
if echo $0 | grep -e ^\- 2>&1>/dev/null; then echo "This is login shell."; else echo "This is non-login shell."; fi;
Let's combine the two above together to get both pieces of information at once:
THIS_SHELL_INTERACTIVE_TYPE='non-interactive'; THIS_SHELL_LOGIN_TYPE='non-login'; if tty -s; then THIS_SHELL_INTERACTIVE_TYPE='interactive'; fi; if echo $0 | grep -e ^\- 2>&1>/dev/null; then THIS_SHELL_LOGIN_TYPE='login'; fi; echo "$THIS_SHELL_INTERACTIVE_TYPE/$THIS_SHELL_LOGIN_TYPE"
ssh firstname.lastname@example.org Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-1083-aws x86_64) ubuntu@ip-172-31-0-70:~$ THIS_SHELL_INTERACTIVE_TYPE='non-interactive'; ubuntu@ip-172-31-0-70:~$ THIS_SHELL_LOGIN_TYPE='non-login'; ubuntu@ip-172-31-0-70:~$ if tty -s; then THIS_SHELL_INTERACTIVE_TYPE='interactive'; fi; ubuntu@ip-172-31-0-70:~$ if echo $0 | grep -e ^\- 2>&1>/dev/null; then THIS_SHELL_LOGIN_TYPE='login'; fi; ubuntu@ip-172-31-0-70:~$ echo "$THIS_SHELL_INTERACTIVE_TYPE/$THIS_SHELL_LOGIN_TYPE" interactive/login
ubuntu@ip-172-31-0-70:~$ bash -c 'THIS_SHELL_INTERACTIVE_TYPE='non-interactive'; THIS_SHELL_LOGIN_TYPE='non-login'; if tty -s; then THIS_SHELL_INTERACTIVE_TYPE='interactive'; fi; if echo $0 | grep -e ^\- 2>&1>/dev/null; then THIS_SHELL_LOGIN_TYPE='login'; fi; echo "$THIS_SHELL_INTERACTIVE_TYPE/$THIS_SHELL_LOGIN_TYPE"' interactive/non-login
ssh email@example.com < checkmy.sh Pseudo-terminal will not be allocated because stdin is not a terminal. Welcome to Ubuntu 16.04.5 LTS (GNU/Linux 4.4.0-1083-aws x86_64) non-interactive/login
ssh firstname.lastname@example.org 'THIS_SHELL_INTERACTIVE_TYPE='non-interactive'; THIS_SHELL_LOGIN_TYPE='non-login'; if tty -s; then THIS_SHELL_INTERACTIVE_TYPE='interactive'; fi; if echo $0 | grep -e ^\- 2>&1>/dev/null; then THIS_SHELL_LOGIN_TYPE='login'; fi; echo "$THIS_SHELL_INTERACTIVE_TYPE/$THIS_SHELL_LOGIN_TYPE"' non-interactive/non-login
You can explicitly request interactive shell when you want to run command remotely via ssh by using
ssh email@example.com -t 'THIS_SHELL_INTERACTIVE_TYPE='non-interactive'; THIS_SHELL_LOGIN_TYPE='non-login'; if tty -s; then THIS_SHELL_INTERACTIVE_TYPE='interactive'; fi; if echo $0 | grep -e ^\- 2>&1>/dev/null; then THIS_SHELL_LOGIN_TYPE='login'; fi; echo "$THIS_SHELL_INTERACTIVE_TYPE/$THIS_SHELL_LOGIN_TYPE"' interactive/non-login
Note: On topic why running command remotely is not
login shell more info here.
This is old thread, but I have just found a concrete example of non-login interactive shell.
When I was using VSCode for remote developement on Linux VM, I realized that my environment variables in
/etc/profile.d/env_file.sh were not picked up by VSCode integrated terminal, even after restarting VSCode and terminal itself. Output for
$0 indicated that it is not a login shell.
It seems like, after connecting to remote Linux machine, VSCode was just starting one main login shell, but for every integrated terminal it launched just another
/bin/bash process. You see the output of
ps doesn't have a
-, but that was inconclusive to decide whether the current shell is login or not.
$ echo $0 /bin/bash $ ps $$ PID TTY STAT TIME COMMAND 2274 pts/3 Ss 0:00 /bin/bash
When I login to server directly over SSH, I see
-bash, that is a login shell.
~$ echo $0 -bash ~$ ps $$ PID TTY STAT TIME COMMAND 2088 pts/2 Ss 0:00 -bash
Then I added shell argument (
-l) option in VSCode. Now, output for echo
$0 is same, but notice the
$$ (Id of the current process, PID)
/bin/bash is with
--login ) option.
$ echo $0 /bin/bash $ ps $$ PID TTY STAT TIME COMMAND 2309 pts/3 Ss 0:00 /bin/bash -l
So, to check whether shell is login shell or not, you need to check both
echo $0 and
ps $$. Depending on the implementation, output should be either
Gilles' answer is great, but was tricky to follow for me, so here's the gist of what I personally needed to know in a simpler language.
A login shell is the shell given to the user upon their login. So only one of it will exist after the user has logged in.
A non-login shell is a shell invoked without the interference of the login process. Non-login shells do not need to be one per login, so you may get any number of them after you log in.
The distinction between the two lets the users bind the tasks that they need to be executed only once per their login (e.g. some heavy tasks) to the startup of their login shell, and avoid having them fired upon the creation of every new non-login shell (of which they might need numerous ones). Things will be much clearer (hopefully) through the two examples below.
Create a new user first (DON'T switch to it afterwards):
sudo adduser foo
then become root:
and add this line to
echo "Echoed on start of $USER's login shell"
Now switch the new user using su like this (Guess what will happen first):
You won't see anything echoed. You might have expected the echo you added to the file
/home/foo/.profile to run, because this file is a login-triggered file, but the problem is not with this file, the problem is you never logged in to foo at all (yes, although it prompted you for foo's password).
So although you can switch to other users using the command
su <username>, it's not a "login" whatsoever. It's just that after switching, whenever you run commands, that user's ID and group ID will be used instead of yours'. I.e. according to the
su allows to run commands with a substitute user and group ID.
So how can we login to a user (and get the login shell of that user as well) when switching to them? We read in the
man su that we can use one of the options
-, -l, --login !
-, -l, --login: Start the shell as a login shell...
So at this point if you
exit, you will NOT be logged out of "foo", you will be logged out of your own user! So instead of
exiting, let's switch back to our own user using
now run this command instead:
su --login foo
and give foo's password. This time, you will see this line echoed:
Echoed on start of foo's login shell
Now you can run
exit too, to logout of foo and switch to your own login shell.
Add this line to your
echo "start of $USER's login shell"
save, then open a new terminal, you will see it echoed. Now run:
You must see (assuming you're on Bash):
As you see, it's the name of your shell program prepended with a dash, it means it is a login shell. Now run:
You won't see anything echoed, but from this point on, you'll not be in the same Bash as before, you just received a new non-login bash shell. To confirm:
Now it must not be prepended by a dash anymore, i.e.:
Where you may want to use this? For instance, when you need to run some commands by passing them as an "argument" to bash. A famous example of this is using "sudo" behind a command that does redirection to a file requiring sudo privileges:
sudo echo 'example line' >> /path/to/file/requiring/sudo/privileges
Although sudo will run that command as root, still the redirection will be executed earlier (to open the file and prepare it for redirection), so a nice "Permission Error" will be printed and the whole command will fail. Instead, you can use a non-login shell as root and pass your whole command to it as a single argument. E.g.:
sudo bash -c "echo 'example line' >> /path/to/file/requiring/sudo/privileges"
Of course you don't want root's startup files to fire each and every time you execute such commands!