Each user has an associated shell
cat /etc/passwd | grep '^m:'
m:x:1000:1000:M:/home/m:/bin/sh
By default, on an Alpine system /bin/sh
points to the busybox ash.
ls -l /bin/sh
lrwxrwxrwx 1 root root 12 Jun 8 19:48 /bin/sh -> /bin/busybox
A shell which can be linked to from
/bin/sh
is expected to be a POSIX compliant shell.
The shell can be changed using chsh
.
chsh
requires the shell to be listed in/etc/shells
. We can directly change/etc/passwd
too for the same outcome.
From man chsh
NAME
chsh - change login shell
DESCRIPTION
The chsh command changes the user login shell. This determines the name
of the usr's initial login command.
Which tells us that this can be any program, not necessarily a “shell”.
This program is run (by /bin/login
) when the user logs in the console or a
tty. Since it is usually a shell, hence the name “login shell”.
The aforementioned program can be run in other contexts too. In such contexts it is then referred to as a “non-login shell”.
If it is indeed a “shell”, then usually it behaves in a manner expected of shells.
From the POSIX standardopengroup.org:
The following environment variables shall affect the execution of sh:
ENV. This variable, when and only when an interactive shell is invoked, shall be subjected to parameter expansion by the shell, and the resulting value shall be used as a pathname of a file containing shell commands to execute in the current environment.
So we can put our customizations in some file, say ~/.myrc
, and if we set
ENV
to point to ~/.myrc
when our shell is invoked, it will get sourced.
echo "echo hello" >~/.myrc
ENV=$HOME/.myrc sh
hello
Note the use of “$HOME” instead of “~”. Tilde expansion might not be available at this point in the initialization sequence.
But where do we set ENV
?
If it is indeed a “shell”, then usually it also behaves in a manner customary to shells; a manner set by the behaviour of the first shell, the Bourne shell.
Some of this behaviour is POSIXified, some not (or maybe I can’t find the
corresponding reference). Either way, one thing that shells following in the
tradition of the read /etc/profile
and ~/.profile
when it is invoked as a
“login shell”.
How does a shell know it is the login shell? Apparently, the
/bin/login
(the command that authenticates the user when they login
on the console or a tty) passes arg[0]
by prefixing it with “-“ when
invoking the shell (e.g. exec_login_shell
in busybox source).
We can however also nicely ask the shell to behave as if was being invoked as
the login shell by passing the “-l” flag (short for “–login”, but not all
shells recognize the longer form). Doing so, we can see it reading ~/.profile
.
echo "echo HELLO" >> ~/.profile
sh -c exit # prints nothing
sh -l -c exit
HELLO
Similarly, when invoked as a login shell a Bourne-compatible shell will also
read /etc/profile
. From the arch
wiki:
A login shell is an invocation mode, in which the shell reads files intended for one-time initialization, such as system-wide
/etc/profile
or the user’s~/.profile
or other shell-specific file(s).
So we now have a mechanism for getting our shell to configure the environment and do other things when we invoke it.
export ENV=/path/to/our/custom/shell/commands
in ~/.profile
./path/to/our/custom/shell/commands
.This is indeed the mechanism we need to use for the default shell that comes with Alpine, the busybox ash.
export ENV=$HOME/.ashrc
What happens to the login shell itself? The
ENV
will come into effect for any subsequent shells launched when theENV
is set, but for the login shell itself,/path/to/our/custom/shell/commands
will never be sourced sinceENV
wasn’t set when it was started. Or will it?Turns out (not coincidentally, likely) that this scenario is handled, at least by ash. From the source (ash.c):
/* * Read /etc/profile, ~/.profile, $ENV. */ static void read_profile(...
Since they are read in sequence, and since we set ENV in
~/.profile
,$ENV
will be set and sourced at the end of the sequence, and everything will work out in the end.
However, since this is a basic requirement, its bigger siblings like bash and
zsh provide a way to skip this two step dance by reading special “rc” files
automatically, whether they are invoked as login shells or not. For example, zsh
will read ~/.zshrc
.
zsh reads /etc/profile, ~/.profile and $ENV only if it is running in sh compatibility mode.
When logging in using a display manager, we never run a shell (until we do), so who sources the profiles then, or are they?
From zsh FAQ:
Login shells are often interactive, but this is not necessarily the case. It is the programme that starts the shell that decides if it is to be a login shell, and it is not required that the shell be run interactively. A possible example is a display manager that starts a shell to initialize your environment before running the window manager to create terminals: it might run this as a login shell but with no terminal, so it is not interactive.
Our display manager, and the mechanism seems to be via
/usr/bin/lightdm-session
:
readlink /usr/bin/lightdm-session
/etc/X11/xinit/Xsession
In /etc/X11/xinit/Xsession
we find
# Load profile
for file in /etc/profile "$HOME/.profile" /etc/xprofile "$HOME/.xprofile"; do
if [ -f "$file" ]; then
echo "Loading profile from $file";
. "$file"
fi
done