Granted, init-related scripts have become complex along the years, but we may argue that happened because of the same type of reasoning that's now justifying the move to systemd: the need to do everything in one place and automagically.
You know, even if I would agree with you that "shell itself is an ugly mess of weird syntax, gotchas, and corner cases" that doesn't preclude init scripts from being redone in something other than shell.
"People who don't understand unix are bound to reinvent it, badly."
This has become the tag line for Linux, and unfortunately is starting to reach the previously sacred grounds of basic (server) infrastructure.
The problem is not that they're shell scripts. The problem is that they are not constrained in a way that makes it possible to parse them easily and know what the state of the system is expected to be before and after processing.
Instead you're forced to blindly execute them, and hope the script handles all the appropriate corner cases. In 20 years of maintaining Linux servers, it rarely goes more than days between situations where I come across init scripts that fails to account for some situation in ways that causes me aggravation.
It's not uncommon to come across init scripts that regularly fails to stop a service because it has no proper way of determining which pid the process has, for example.
Just the other day I had to deal with a server where the init scripts happily let 3 instances of a server process trying to operate on the same dataset; thankfully the app locked the files in questions and were just screeming bloody murder in its logs, but they were also all three competing for various resources. The init system of course had absolutely no way of telling that something had gone wrong.
The only solution to have a sane server setup with SysV-init is to not use it for anything but the basic tasks and to start a proper process monitor. Then I'd rather just get rid of init entirely, since you're pulling in a bunch of other code that can do almost all of init's tasks anyway.
I argue that shell scripts became more complicated because of quite the opposite of what you're claiming. The original Bourne shell already supported, piping, control structures, etc. So people thought, hey, why design a new init system that does everything properly when we can just push all this logic to shell scripts and have them act as glue?
It's the perfect example of "worse is better". They chose to keep the core init system simple, but as a result everything else became a complicated mess that doesn't handle corner cases properly. vidarh's comment is spot on.
Towards the idea that you can write them in another language, we in the Perl community ended up developing something of that nature[1]. It was originally intended to make writing daemons in Perl easier by pulling out common functionality (double fork and such), and giving a consistent clean interface. One of the bigger requested things was the ability to use it as basically the entire init script ([2] for an example).
You know, even if I would agree with you that "shell itself is an ugly mess of weird syntax, gotchas, and corner cases" that doesn't preclude init scripts from being redone in something other than shell.
"People who don't understand unix are bound to reinvent it, badly."
This has become the tag line for Linux, and unfortunately is starting to reach the previously sacred grounds of basic (server) infrastructure.