~/.login_conf is the better shell‐agnostic way to do things.

Background: Why you're possibly looking at multiple shells.

The Bourne‐family shell that you get out of the box on the BSDs is either the (PD) Korn shell on OpenBSD, the MirBSD Korn shell on MirBSD, or a local derivative of the Almquist shell on the other BSDs. And that's your login shell, out of the box. It's also the shell that implements sh, the default (Bournist) shell.

The PD Korn shell is fairly close to a standard shell, as the actual Korn 93 shell was one of the existing shells whose behaviour the POSIX people looked to. It does not do some of the things that the Korn 93 shell does, however, such as loadable built‐in commands from shared libraries. But like the Korn 93 shell it does do several things beyond what the standard sh is required to do at minimum.

The BSD flavours of the Almquist shell are not the same as the (two — it is complex) Debian Almquist shells. The Debian Almquist shells are compiled with some features turned off. The most noticeable turned off feature is editline; and on a Debian system the Almquist shells use the fundamental ‘cooked’ a.k.a. ‘canonical’ input mode of the terminal line discipline, which is almost never seen in a modern shell in its interactive mode. A normal Almquist shell, as on the BSDs, has editline turned on, and that makes it far better as a login shell, used interactively. Of course, Debian does not intend the Debian Almquist shells for interactive use. If you think that you know the Almquist shell from the Debian Almquist shells in the GNU world, though, you actually do not.

Back in the 1980s, people actually got the C shell as their login shell on BSD. The C shell was, after all, another of Bill Joy's inventions in the CSRG at UCB. But the C shell (even its TENEX flavour) had some very noticeable problems, that caused people to go off it.

So gradually that regressed to only the superuser account root having the C shell as its login shell, with a toor account also being the superuser but having a Bournist shell. The last hold‐out that did this was FreeBSD, which in 2021 finally changed root's login shell to be a Bournist shell, too.

What you will not get, however, is a Bourne‐family shell like the GNU Bourne Again shell, or the Z shell, or the Watanabe shell. Although the BSD Almquist shell variants are better than the Debian ones, and although the PD Korn shell is actually a fairly powerful tool, people coming from the GNU world want to stick with their favourite login shell for interactive use.

So after installing the Bourne Again, Z, Watanabe, or whatever shells from packages, or ports, the problem becomes twofold:

login.conf, a shell‐agnostic replacement for profiles

The BSDs have a mechanism known as login.conf. There is a system‐wide configuration file in /etc, and each user can also set up a local configuration file in xyr home directory. These are more capabilities files that are turned into Berkeley DB databases. /etc/login.conf is actually /etc/login.conf.db to the system proper, and ~/.login_conf is likewise actually ~/.login_conf.db.

The advantage of this system is that the twain are processed by the same process that goes on to be the login shell in a session, that would then be processing one of the many different shell startup scripts. So it is a drop‐in replacement for setting login session environment variables (and umasks and whatnot) in a ‘profile’ shell startup file, that happens in the right place.

Where one would have this in several ‘profile’ shell startup files:

export TZ=Europe/London
export VISUAL=vim
export LANG=en_GB.UTF-8
export PATH="$HOME:/bin:/usr/local/sbin:/usr/local/bin:/usr/pkg/sbin:/usr/pkg/bin:/usr/sbin:/usr/bin:/sbin:/bin"
umask 077

one instead has this once in ~/.login_conf

me:\
	:timezone=Europe/London:\
	:setenv=VISUAL=vim:\
	:lang=en_GB.UTF-8:\
	:path=~/bin /usr/local/sbin /usr/local/bin /usr/pkg/sbin /usr/pkg/bin /usr/sbin /usr/bin /sbin /bin:\
	:umask=077:

and it applies the same whatever the login shell is.

The same goes for the system administrator wanting to set such stuff globally, in the face of users who do not have the one shell that came in the box. Rather than repeating the same things across /etc/profile, /usr/pkg/etc/profile, /usr/local/etc/profile, and /usr/local/etc/zprofile, and then wondering what to do about the Watanabe shell, the system administrator can just put all of this in /etc/login.conf, compile that to the database form, and be done for whatever shell a user might pick as xyr favourite login shell. Even those mad retrocomputerists who want to log in to the C shell, which not only has different startup files but an entirely different syntax for setting environment variables.

Tip: Unless you are running a massively multi‐user system with users from multiple countries and cultures, you will not need to care one whit about the ‘login class’ part of login.conf. Just set up the default login class and the root login class to :tc=default: to it. Similarly, all that users have to care about in ~/.login_conf is me.

How do you solve a problem like Maria OpenSSH?

There is only one problem: OpenSSH does not understand login.conf.

I solved this by writing unsetenv, a chain‐loading utility that can unset environment variables; userenv, a chain‐loading utility that processes login.conf; and login-shell, a utility that made it trivial to re‐invoke the shell from $SHELL as a login shell. My SSH clients are given the -t option and told to run, on the host:

unsetenv PATH userenv --default-path --default-tools --default-timezone --default-locale --set-dbus --set-xdg --set-other --toolkit-pager login-shell

What is all of the extra stuff? Well, along the way I added XDG environment variables and some extra login.conf capabilities that were handier than bunging everything into a massive setenv= capability:

	:termpath=/etc/system-control/convert/termcap/termcap.linux /etc/system-control/convert/termcap/termcap.interix /etc/system-control/convert/termcap/termcap.teken /etc/system-control/convert/termcap/termcap.jfbterm /etc/system-control/convert/termcap/termcap.ms-terminal /usr/share/misc/termcap:\
	:manpath=/usr/local/man /usr/pkg/man /usr/share/man /usr/share/openssl/man:\
	:manpager=less -R:\
	:visual=vim:\

Won't the normal login program not understand all of this extra stuff? Well, handily I had already replaced that with something where userenv dropped easily right into place.

Yes, all of my tools work on Linux‐based operating systems as well, and (because I wrote a noddy parser library for BSD capabilities files, that did just enough) I thus have the power of login.conf in the face of multiple shells on Linux‐based operating systems, too. But you're not here for that. ☺


© Copyright 2026 Jonathan de Boyne Pollard. "Moral" rights asserted.
Permission is hereby granted to copy and to distribute this web page in its original, unmodified form as long as its last modification datestamp is preserved.