At work, there was a shell script someone had written. It downloaded and installed the packages we needed, adjusted config files, and brought the service up with systemctl. Roughly, it looked like this:
install_blabla() {
dest=$(mktemp /tmp/blabla_XXXX.deb)
curl -fsSL http://blabla.com/blabla.deb -o $dest
dpkg -i $dest
rm -f $dest
}
install_conf() {
curl -fsSL http://blabla.com/blabla.conf -o /etc/blabla/blabla.conf
sed -i -e "..." /etc/blabla/blabla.conf
}
start_blabla() {
systemctl start blabla
}
do_install() {
install_blabla
install_conf
start_blabla
}
do_install
If you follow that script, the flow is:
- Download
blabla.debwithcurl - Install it with
dpkg - Download
blabla.confwithcurl - Edit
blabla.confwithsed - Start
blablawithsystemctl
If everything runs in that order, blabla should obviously start with the modified config. But we got a report that blabla was behaving oddly, and it looked as if it was loading the default config rather than the one we had changed.
No matter how we checked, the config file on disk really was updated correctly, and it wasn’t reading some other path either.
Then one suspicion suddenly flashed through my mind.
“What if, for some unknown reason, the download of blabla.conf hadn’t finished before the next line ran?”
So I restarted blabla, and sure enough, it came up using the edited config. Suddenly I was thoroughly confused.
“Wait. Bash isn’t Node.js. How could curl not finish before the next statement runs? Bash isn’t async!”
I googled it just in case there was something I didn’t know, found nothing, and remained baffled about what had actually happened, until a colleague sitting next to me nailed it.
“This looks like it’s starting right after dpkg installs it.”
And that, of course, was the real cause. The problem was not Bash running asynchronously. On Ubuntu, when you install a package with dpkg, if the package ships with a service, it can be enabled and started automatically as part of the install.
So in the end, I had doubted Bash for absolutely no reason.
Sorry, Bash.