Jul 14, 2015 Tags: programming, workflow
As I mentioned in my very first post,
I do a lot of configuration. In particular, I have quite a few bash
functions,
aliases, and environment settings that I update regularly to reflect my needs.
To synchronize my configurations across all of my computers, I use a
git repository and a
long and not very pretty
bash
script that essentially maintains clones of the dotfiles repo and copies
files and directories from it depending on the needs of the host system.
As a result, updating a given host’s configuration only requires a single command:
Unfortunately, this only performs a file-level synchronization of
configurations. This is perfectly fine for programs that are run frequently
(like git
and feh
) or that poll for their configuration files while running,
but it presents a problem for bash
itself, which normally only reads ~/.bashrc
(and/or ~/.bash_profile) once per session.
Until now, my solution was a shell alias I named bashreload
:
1
alias bashreload='unalias -a ; source ~/.bashrc'
This works, but only for a single shell at a time. In other words, reloading
my bash
configuration across my (very numerous) terminal sessions would require
me to go to each and manually type bashreload
, wasting time and effort to do
something that should be instantaneous and uninvolved.
just a few bash processes.
To solve this, I began to experiment with good old
Unix signals, which are actually very
easy to manage with bash
’s trap
and kill
builtins:
1
2
3
4
5
6
7
8
function handler()
{
echo "received signal"
}
trap handler SIGUSR1
kill -USR1 $$ # -> "received signal"
Knowing this behavior, I whipped up an initial solution in my ~/.bashrc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
alias getconfigs='dotfiles ; allreload' # dotfiles is the syncing script
trap bashreload SIGUSR1
# allreload - send SIGUSR1 to every bash process, which is trapped to bashreload
function allreload()
{
pids=$(pidof bash)
[[ -n "${pids}" ]] && kill -SIGUSR1 ${pids}
}
# bashreload - wipe aliases and re-source from ~/.bashrc
function bashreload()
{
unalias -a
source ~/.bashrc
}
However, as I soon learned, this has a very bad side effect. Because the
allreload
function collects the PIDs of all bash processes, including
non-interactive ones, it ends up sending SIGUSR1 to non-interactive bash
instances. What is bash
’s default SIGUSR1 behavior, I hear you ask?
When untrapped, a bash
process that receives SIGUSR1 is immediately killed.
As a result I ended up killing several non-interactive scripts, one of which was apparently vital to Compiz and whose death caused all windows on my left monitor to stop responding.
This presents a major problem: How can interactive and non-interactive bash
processes be distinguished externally, with no access to the normal $PS1
or
$-
variables?
Ultimately, as far as I can tell, the above is simply not possible. We could produce a similar effect with an IPC system other than signals, but the results would almost certainly be ugly, difficult to maintain, and error prone.
I was about to give up and revert to my original system of running bashreload
on demand when IRC saved me once again:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<yossarian> looks like this idea is going down the toilet
<yossarian> unless i do IPC without signals
<ham-peas> well
<ham-peas> i suppose you could have all your login shells start listening to a file or fifo somewhere at init time, and reinit themselves when they see something written to it
<ham-peas> but that would be insane and disgusting
<yossarian> yeah, not doing that
<yossarian> if only the default behavior wasn't kill
<yossarian> i could override another signal maybe
...
<ham-peas> the only one i see in signal(7) that a) has default action ignore, and b) isn't already used by bash, is SIGURG
<yossarian> hmm
<ham-peas> which i've never actually heard of before, for whatever that's worth
<ham-peas> and 'kill -URG $$' doesn't seem to do my shell any harm
<ham-peas> so there's that
<yossarian> hooray
<yossarian> let's see if it works
SIGURG
(which is defined in POSIX.1-2001 and is normally
used to signal out-of-band data on a TCP/IP socket),
has a default action of “Ignore” according to signal(7)
, an action that bash
appears to respect. This is confirmed by its absence from
terminating_signals[]
in the bash
source tree.
Therefore, a quick modification of three lines:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
alias getconfigs='dotfiles ; allreload' # dotfiles is the syncing script
trap bashreload SIGURG
# allreload - send SIGURG to every bash process, which is trapped to bashreload
function allreload()
{
pids=$(pidof bash)
[[ -n "${pids}" ]] && kill -SIGURG ${pids}
}
# bashreload - wipe aliases and re-source from ~/.bashrc
function bashreload()
{
unalias -a
source ~/.bashrc
}
…and it works!
In action:
After a little bit of tinkering, I can now:
bash
processes.bash
processes with my newest aliases,
functions, environment, and so forth with a single command.Of course, this still isn’t great. Unix signals are slow, and using a signal
(SIGURG
) to do something completely orthogonal to its original purpose
probably isn’t a good idea (especially if bash
suddenly adds it to its
terminating_signals[]
array). Then again, this is bash
we’re talking about -
performance isn’t going to be great in the first place, and it works!
¯\_(ツ)_/¯
- William