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?
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?
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: /etc/profile
and ~/.profile
for the traditional Bourne shell, ~/.bash_profile
additionally for bash†, /etc/zprofile
and ~/.zprofile
for zsh†, /etc/csh.login
and ~/.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 /etc/profile
and ~/.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 bash
, /etc/zshrc
and ~/.zshrc
for zsh, /etc/csh.cshrc
and ~/.cshrc
for csh, the file indicated by the ENV
variable for POSIX/XSI-compliant shells such as dash, ksh, and bash when invoked as sh
, $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 /etc/zshenv
and ~/.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.
bash
as a non-interactive login shell?
Commented
Jun 16, 2013 at 8:47
FOO
is an environment variable (i.e. .profile
contains export FOO=something
) then it's available to all subprocesses, including foo.sh
. If you change .profile
to export FOO=something_else
then ./foo.sh
will still print something
until the next time you log in.
Commented
Oct 6, 2016 at 15:13
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 shopt login_shell
:
prompt> shopt login_shell
login_shell off
(or 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 man bash
)
"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
~/.bash_profile
,~/.bash_login
, and~/.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."
A login shell is one whose first character of argument zero is ‘-’, or one invoked with the --login option.
. According to this definition, echo $0
couldn't determine whether a shell is in login shell or not because when we start a shell with bash --login
, $0
is set as bash
(filename used to invoke Bash), not -bash
.
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: bash
).
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 [email protected]
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 protected] < 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 [email protected] '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
-t
switchYou can explicitly request interactive shell when you want to run command remotely via ssh by using -t
switch.
ssh [email protected] -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.
In a login shell, argv[0][0] == '-'
.
This is how it knows it's a login shell.
And then in some situations it behaves differently depending on its "login shell" status. E.g. a shell, that is not a login shell, would not execute a "logout" command.
man bash
, with emphasis added, "A login shell is one whose first character of argument zero is a -, or one started with the --login option."
A shell started in a new terminal in a GUI would be an interactive non-login shell. It would source your .bashrc, but not your .profile, for example.
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.
Example 1:
Create a new user first (DON'T switch to it afterwards):
sudo adduser foo
then become root:
sudo -i
and add this line to /home/foo/.profile
:
echo "Echoed on start of $USER's login shell"
Now switch the new user using su like this (Guess what will happen first):
su foo
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 man su
:
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 exit
ing, let's switch back to our own user using su
again:
su <your_own_user_name>
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.
Example 2:
Add this line to your ~/.profile
:
echo "start of $USER's login shell"
save, then open a new terminal, you will see it echoed. Now run:
echo $0
You must see (assuming you're on Bash):
-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:
bash
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:
echo $0
Now it must not be prepended by a dash anymore, i.e.:
bash
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!
~/.bashrc
and in both cases echo $0
will print bash
and not -bash
, as this is not a login shell.
Commented
Jun 18, 2022 at 0:03
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 -l
( --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 -bash
or /bin/bash -l
Almost all the answers here are missing on how to check a login shell (only Timothy is close but still technically incorrect).
echo $0
is not a surefire way to check it. You can see this simply by calling bash --login
or zsh --login
. Even though it's a login shell echo $0
will not show the hyphen (-
).
Hyphen indeed means a login shell, but no hyphen does not mean it's not a login shell.
A correct way to check
bash: shopt login_shell
zsh: setopt | grep login
or set -o | grep login
These two are just examples. I'm sure other shells have their own commands to check shell options.