Uh, my above post is wrong, of course, it should be piped through sed. I must
have cut-n-pasted the wrong line. Ahem.
diff ... | sed -n 's/^> \(.*\)$/\1/p' > ~/.bashrc-update
(Although, a unified diff might be better.)
However, it may or may not be worth doing this, depending on how long and/or
complex your ~/.bashrc is and how many shells you have running.
One important point, of course, is that because diff is line-based, you can
only use this method for single line changes. If you change, say, the contents
of a for loop, then obviously it's not going to work.
But if you keep your aliases and environment variables in a seperate file,
this could save some time if, say, you are only adding/modifying a single
variable.
As for getting the shells to reload the config file(s), the best way to do
this is to use a signal, say SIGUSR1, to automatically reload the file.
e.g. in ~/.bashrc, you put something like:
Code:
trap '. ~/.bashrc' USR1
you can then killall -USR1 bash. This way, you don't need to periodically
check for updates, you just signal every shell when you make changes.
Naturally, you'd use a shell wrapper to combine these steps:
Code:
#!/bin/sh
$EDITOR ~/.bashrc
killall -USR1 bash
You could also check the mtime of ~/.bashrc, so if you changed your mind and
exited without modifying anything, it wouldn't cause an unnecessary update:
Code:
#!/bin/sh
mtime=$(stat -c %Y ~/.bashrc)
$EDITOR ~/.bashrc
if [[ $(stat -c %Y ~/.bashrc) > $mtime ]]; then
killall -USR1 bash
fi
To imporove efficiency, you could split ~/.bashrc into seperate files as I
suggested before. You might use files like ~/.bash/alias, ~/.bash/env, etc
(you can call the files whatever you want, of course, such as ~/.bash-aliases
but using a subdirectory reduces the clutter in $HOME).
E.g. bash-update.sh:
Code:
#!/bin/sh
files=()
bashrc=1
if [[ -z $1 ]]; then
files=(~/.bash/alias ~/.bash/env)
else
for f in $@; do
case $f in
(alias|env) files=(${files[*]} ~/.bash/"$f"); bashrc="";;
(rc|bashrc) files=(); bashrc=1;;
esac
done
fi
$EDITOR ${files[*]}
# There are any number of ways to tell bash which files have changed, we'll
# keep it simple:
rm -f ~/.bash/.update
# If you edit ~/.bashrc, that will included the alias and env files anyway, so
# we don't add them to the update file, even if the've changed, otherwise we'd
# end up reading them twice.
if [[ -n $bashrc ]]; then
echo ~/.bashrc >> ~/.bash/.update
else
for f in ${files[*]}; do
echo $f >> ~/.bash/.update
done
fi
killall -USR1 bash
Now, we need to update the trap function in ~/.bashrc:
Code:
reload-config()
{
if [[ -f ~/.bash/.update ]]; then
while read file; do
. $file
done < ~/.bash/.update
fi
}
trap reload-config USR1
Now you can run the script naming whichever file or files you need to edit. If
you don't specify any files, it will edit all of them.
bash-update [rc|alias|env]
Thus, when you exit the editor, all running shells will be updated
automatically, and will only re-read files that have been edited (though if
you changed ~/.bashrc, that will obviously read in all the other files too).
You could extend this to include the mtime check.
Once thing to note is that the ~/.bash/.update file gets left lying around
afterwards. It get overwritten when you make new changes, so it shouldn't
cause a problem, but obviously isn't ideal. The problem is that you can't just
delete it any old time, because obviously every shell has to include it before
it can be deleted, and you don't know when all the shells have finished
reading it. One solution is to use reference counts on the file (i.e. hard
links). For each running shell, create a hard link to ~/.bash/.update-$pid and
rm that link after that shell has included the file.
To incorporate this, change the reload function to:
Code:
reload-config()
{
if [[ -f ~/.bash/.update-$$ ]]; then
while read file; do
. $file
done < ~/.bash/.update-$$
rm ~/.bash/.update-$$
fi
}
and extend the script:
Code:
#!/bin/sh
files=()
bashrc=1
for f in $@; do
case $f in
(alias|env) files=(${files[*]} ~/.bash/"$f"); bashrc="";;
(rc|bashrc) files=(~/.bashrc); bashrc=1;;
esac
done
if [[ -z $files ]]; then
files=(~/.bashrc ~/.bash/alias ~/.bash/env)
fi
$EDITOR ${files[*]}
# There are any number of ways to tell bash which files have changed, we'll
# keep it simple:
rm -f ~/.bash/.update
# If you edit ~/.bashrc, that will included the alias and env files anyway, so
# we don't add them to the update file, even if the've changed, otherwise we'd
# end up reading them twice.
if [[ -n $bashrc ]]; then
echo ~/.bashrc >> ~/.bash/.update
else
for f in ${files[*]}; do
echo $f >> ~/.bash/.update
done
fi
pidlist=($(ps ax -o pid,comm | grep bash | awk '{print $1}'))
for p in ${pidlist[*]}; do
ln -f ~/.bash/.update ~/.bash/.update-$p
done
killall -USR1 bash
rm ~/.bash/.update
If you have a lot of aliases and/or environment variables, you could also
incorporate the diff technique only for the alias and env files (this time
~/.bash/.update contains code that we eval, rather than a list of files).
Obviously, you must keep each assignment on a single line. If you need
different aliases for different systems, e.g.
Code:
sys=$(uname -s)
if [[ $sys == Linux ]]; then
alias so_and_so='whatever -some_opt'
elif [[ $sys == Solaris ]]; then
alias so_and_so='whatever -different_opt'
fi
then you must put that in ~/.bashrc or some other file (or make sure never to
modify it using the update script).
But, providing that everything in those files is line-based, this should work:
Code:
#!/bin/sh
files=()
bashrc=~/.bashrc
update_dir=~/.bash/.update.d
mkdir -p $update_dir
if [[ -z $1 ]]; then
files=(~/.bash/alias ~/.bash/env)
else
for f in $@; do
case $f in
(alias|env) files=(${files[*]} ~/.bash/"$f"); bashrc="";;
(rc|bashrc) files=(); bashrc=~/.bashrc;;
esac
done
fi
rcmtime=$(stat -c %Y ~/.bashrc)
mtime=()
for f in ${files[*]}; do
mtime=(${mtime[*]} $(stat -c %Y $f))
cp $f $update_dir/${f##*/}-tmp-$$
done
$EDITOR $bashrc ${files[*]}
# There are any number of ways to tell bash which files have changed, we'll
# keep it simple:
rm -f ~/.bash/.update
# If you edit ~/.bashrc, that will included the alias and env files anyway, so
# we don't add them to the update file, even if the've changed, otherwise we'd
# end up reading them twice.
if [[ -n $bashrc ]]; then
if [[ $(stat -c %Y ~/.bashrc) > $rcmtime ]]; then
echo 'source ~/.bashrc' >> ~/.bash/.update
fi
else
for i in $(seq 0 $(( ${#files[*]} - 1)) ); do
if [[ $(stat -c %Y ${files[$i]}) > ${mtime[$i]} ]]; then
diff -u $update_dir/${f##*/}-tmp-$$ $f | sed -n 's/^+\([^+].*\)$/\1/p' >> ~/.bash/.update
fi
done
fi
if [[ -f ~/.bash/.update && $(stat -c %s ~/.bash/.update) > 0 ]]; then
pidlist=($(ps ax -o pid,comm | grep bash | awk '{print $1}'))
for p in ${pidlist[*]}; do
ln -f ~/.bash/.update ~/.bash/.update-$p
done
echo UPDATE
cat ~/.bash/.update
killall -USR1 bash
fi
rm -fr ~/.bash/.update $update_dir
which requires a modified reload-config function:
Code:
reload-config()
{
if [[ -f ~/.bash/.update-$$ ]]; then
. ~/.bash/.update-$$
rm ~/.bash/.update-$$
fi
}
Decide for yourself if any of these additional schemes are worth the overhead
compared to a simple edit and blanket re-source. If you have a _lot_ of shells
running (I have over 100...), then sourcing only the modified stuff should
be more efficient, maybe a lot more, especially if your rc file runs a lot of
external commands.