OpenBSD Blog #10: ksh profiles and startup automation
I’ve been on the Bash shell pretty much my entire Unix life. All the way back to my first encounter with Linux hosting on dial-up ISPs in the late 1990s.
I’ve been aware of other shells, but only used them casually off and on.
The standard OpenBSD shell is ksh
(the "public domain Korn shell").
Now that I’m planning to have OpenBSD as a permanent "home base" for my
web development, etc., it’s high time I got more comfortable with ksh
.
The task
For today’s adventure, I wanted to set up some handy aliases for a particular project so I can quickly spin up my development environment with multiple terminals (editing, development, database queries).
To add aliases (or functions), I would normally add them to .bashrc
.
Inevitably, I would then wonder if they should go in .bash_profile
instead.
If if not, why not. The next couple hours would be spent re-reading the same
old resources on the Web; a rabbit-hole of StackOverflow and SuperUser posts,
the Bash manual, etc.
Time to find out how ksh
does it.
(Another part of this task, not specifically related to OpenBSD, is to automate the opening of tabs in my local terminal application which, in turn, will SSH into my OpenBSD box. I’ll get to that at the end.)
.profile and .kshrc
ksh
does not work exactly like Bash. By default it only runs one command file
and under one condition. Namely, if ksh
is being run as a login shell and you
have a ~/.profile
, it runs it.
The concept of ~/.kshrc
exists, but I was surprised to learn that it is
NOT run by default. To execute a ~/.kshrc
file, you would need to specify
it in an ENV
variable.
Furthermore, unlike Bash, if you did define ENV=$HOME/.kshrc
in your
.profile
, then ksh
would reasonably run both startup files because,
as the manual explains:
Of course, since login shells are also interactive, any commands placed in $HOME/.kshrc will be executed by login shells too.
Okay, so, as always, this makes me ask, "Why do I even want to run a .kshrc file anyway? Why can’t I just have .profile?"
To get a satisfying answer, I came up with this pair of experiments:
Experiment One: Just .profile
I’m going to set some things in .profile and see what’s available to sub-shells:
$ cat .profile echo "This is .profile!" # These two lines were originally in .profile: PATH=$HOME/bin:/bin:/sbin:/usr/bin:/usr/sbin:/usr/X11R6/bin:/usr/local/bin:/usr/local/sbin export PATH HOME TERM HELLO='Hello' export HELLO HELLO2='Hello2' alias foo='echo Foo' function honk { echo "HONK!" }
On login, the message prints and the variables, alias, and function all work as expected:
This is .profile! $ echo $HELLO Hello $ echo $HELLO $HELLO2 Hello Hello2 $ honk HONK! $ foo Foo
Then I start up a shell running within the login shell:
$ ksh $ echo $HELLO Hello $ echo $HELLO2 $ honk ksh: honk: not found deimos$ foo ksh: foo: not found
So there you have it, only the exported variable HELLO
is available in
the subshell.
There’s no such thing as exporting aliases and functions. (Today I learned you can export functions in Bash, which seems to be pretty unique to that shell.)
Experiment Two: .profile and .kshrc
To make functions and aliases available to sub-shells in ksh
, you have to
define the name of the startup file to execute in the variable ENV
. This
could be done anywhere, but it makes the most sense to do it upon login,
of course, in .profile
.
So I will add this to .profile
:
export ENV=$HOME/.kshrc
And then create .kshrc
and define a variable, alias, and function:
$ cat .kshrc echo "This is .kshrc!" HELLO3='Hello3' export HELLO3 alias subfoo='echo Subfoo' function subhonk { echo "subHONK!" }
Then I’ll log back in and try out my new items:
This is .profile! This is .kshrc! $ echo $HELLO3 Hello3 $ subfoo Subfoo $ subhonk subHONK!
Yes!
When to use .profile and .kshrc
As a rule of thumb:
-
Use
.profile
for environment variables like$PATH
. -
Use
.kshrc
(and set it inENV
as demonstrated above) for aliases and functions.
This all began because I just wanted to run some stuff at startup as usual. But by the end, I feel like I now understand something that has bothered me for 30 (!) years of logging into Unix-like computers.
In fact, I’m going to make a more general "card" about this: unix-shell-startup Shell startup files (.profile vs .*rc).
Update: I’ve also added Shell startup files that echo their names as a prefix as a result of this experimentation.
Launching a ksh session to run special commands on startup
The next thing I want to get working is the ability to open an SSH session with a specific task ready to go.
I spent a lot of time reading and trying things out and at the moment,
the only method I like sets an environment variable and then launches ksh
.
-
ssh -t <host> <command>
runs an interactive terminal session that begins with the command specified. -
ksh -l
runs ksh as a login shell (which will execute.profile
etc.)
(Note: The -t
option for SSH forces the session to use a pseudo terminal
even thought we’re running a command. This allows us to interact with ksh
.
Otherwise, it’ll be a pretty weird experience with things not echoing the
screen, etc.)
Here we go:
As you can see, the FOO
variable has been made available to the shell process:
ssh -t deimos 'export FOO=bar ; ksh -l' deimos$ echo $FOO bar
Now that I can pass information to the shell, I can act upon it in my .profile script:
$ cat .profile ... echo "FOO=$FOO" if [[ $FOO == 'bar' ]] then echo "Doing 'bar' things..." else echo "Just a normal day." fi
Let’s try a normal login:
$ ssh deimos Last login: Sat Nov 30 15:11:30 2024 from 10.0.0.103 OpenBSD 7.6 (GENERIC.MP) #411: Mon Sep 30 09:04:29 MDT 2024 ... FOO= Just a normal day. deimos$
And then one with FOO
set to bar
:
$ ssh -t deimos 'export FOO=bar ; ksh -l' FOO=bar Doing 'bar' things... deimos$
One step closer!
(Note that OpenSSH can also run a ~/.ssh/rc
, which sounds great at first,
but it actually has a lot of restrictions. See man sshd
. Plus, I’m just
much more likely to remember that .profile
exists!)
This next part isn’t specific to OpenBSD, but it’s a big part of making my dev experience pleasant…
Launching a bunch of SSH sessions from xfce4-terminal
Now that the tricky SSH + shell startup stuff works, the fun part is
using the awesome xfce4-terminal
command line options to open up
a bunch of tabs and have each of them do a different bit of setup
to get my development environment ready for my current project.
The general form is:
xfce4-terminal --tab -T <title> -x <rest of line is command to run>
Which, if run within an existing xfce4-terminal window, will open a new tab, label it with the title, and then run the given command in that tab.
Here’s the lines from my actual shell script:
xfce4-terminal --tab -T 'Vim(Dev)' -x ssh -t deimos 'export DEV=ft1 ; ksh -l' xfce4-terminal --tab -T 'Error Log' -x ssh -t deimos 'export DEV=ft2 ; ksh -l' xfce4-terminal --tab -T 'SQLite' -x ssh -t deimos 'export DEV=ft3 ; ksh -l' xfce4-terminal --tab -T 'Ratf' -x ssh -t phobos
All four open SSH sessions. The first three set an environment variable DEV
,
which is checked in .profile
.
Conclusion
This blog entry spanned portions of two days and I learned a lot in that time. It felt like I was digging a hole deeper and deeper away from my actual project. But now that it works, I’m really happy with this.
These are things I’ve wanted to properly learn for years. Being able to quickly setup a customizable dev environment like this just short of holy grail territory and I’m going to be using it all the time from here on!
My related "card" pages: