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?
– Piotr Dobrogost
Jun 16 '13 at 08:47
echo $FOO
, and $FOO
is defined in ~/.profile
. Why did it printed the value of $FOO
if is a non-interactive, non-login shell?
– IAmJulianAcosta
Oct 06 '16 at 13:42
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.
– Gilles 'SO- stop being evil'
Oct 06 '16 at 15:13
~/.bashrc
?
– d a i s y
Feb 08 '17 at 05:36
~/.pam_environment
, or use something like . ~/.profile; the_real_job
as the command.
– Gilles 'SO- stop being evil'
Feb 08 '17 at 11:57
-
in $0
is not necessary there for all login shells. E.g. bash -li
starts a login shell with $0
simply set to bash
. The -
is set by the process that forks the shell, such as login
or exec -l bash -li
.
– not-a-user
Apr 07 '17 at 06:00
as opposed to ssh example.com my-script-which-is-on-the-remote-machine, which runs a non-interactive, non-login shell
? When I run ssh remotehost $COMMAND
, /etc/profile
is always sourced.
– Hart Simha
Jul 06 '17 at 22:08
ssh example.com <my-script-which-is-stored-locally>
, shouldn't it be <my-script-which-is-stored-locally>|ssh example.com
?
– RubenLaguna
Aug 07 '17 at 14:35
ssh example.com <my-script-which-is-stored-locally
is equivalent to cat my-script-which-is-stored-locally | ssh example.com
. It's part of the command (a redirection operator), not half of a metasyntactic marker.
– Gilles 'SO- stop being evil'
Aug 07 '17 at 15:50
tmux
starts a new shell in an existing session, like screen
, each new shell it starts is a login shell.
– rampion
Sep 14 '17 at 19:22
.profile
, you end up with different environments inside the same tmux session depending on when each window was started.
– Gilles 'SO- stop being evil'
Sep 14 '17 at 23:01
echo $- | bash -lx
(from one of @Gilles' earlier comments) I get the error bash: line 1: himBHs: command not found
. What am I doing wrong?
– kjo
Sep 19 '19 at 01:28
$-
in the current shell and telling the new bash
to run it as a command. I meant to write echo 'echo $-' | bash -lx
or something similar. Weird that nobody remarked on it after all these years!
– Gilles 'SO- stop being evil'
Sep 19 '19 at 06:28
Terminal.app
, I get a login shell (shopt login_shell
retuns login_shell on
), and my .bash_profile
is read (and not .bashrc
, according to scriptingosx). Also, when I su bob
, this time shopt login_shell
returns login_shell off
, and the .bash_profile
of this other user (bob) is not read (e.g. as evidenced by the PS1 which is not as specified in there).
– Antoine
Sep 19 '20 at 16:47
su -
with -
(same as -l
"simulate a full login). From the man
page, it doesn't explicitly say which configuration files are read (.bash_profile
, .bashrc
, etc.).
– Antoine
Sep 19 '20 at 20:50
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
.
– rosshjb
Jul 08 '20 at 19:29
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 ubuntu@34.247.105.87
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 ubuntu@34.247.105.87 < 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 ubuntu@34.247.105.87 '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 ubuntu@34.247.105.87 -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."
– Wildcard
Jan 23 '17 at 11:49
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.
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
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.
– Nils Lindemann
Jun 18 '22 at 00:03
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.
Many places on the web already tell us what are the differences, in terms of what startup files each read; but none of them seems to answer the "why" in a satisfactory and convincing way.
Example use cases where you definitely do not want one or the other behaviour would be great.
– Kal Apr 15 '13 at 03:49