My thoughts on writing a Minecraft server from scratch in Bash
sdomi.pl
sdomi.pl
This is a driver/client for a vintage portable disk drive that has an rs232 interface. Any disk drive obviously has to handle arbitrary data including binary including 0x00.
It's entirely in native bash with no external tools and not even any subshells. It does have to call stty and mkfifo once at startup, but that's just startup like to open the serial port, not part of the work.
https://github.com/bkw777/pdd.sh
The gimmick for reading nulls is not exactly efficient. Basically you read one character at a time and look at the errorlevel from read to detect the difference between "got nothing" and "got 0x00"
It's fine for this case because the drive is so slow and small that even the entire disk is only 200k max. Making bash read one byte at a time for 200k is still nothing today but only because hardware is insane today.
But it's possible. And the beginning of the post does say "as a thought experiment".
Similarly, you don't need xxd to convert back & forth between raw binary and text encoding.
My own similar thought experiment included the idea that, if I'm not going to use any external process I can possibly avoid and only use internal bash features, on the flip side of that, I will squeez bash for all it's worth, allow every bashism possible.
Now that is some serious bash-fu!
Edit:
> Similarly, you don't need xxd to convert back & forth between raw binary and text encoding.
How do you convert binary to text without calling out to `bc'?
xxd and bc examples are plentiful, but not "pure bash". For example, see https://gist.github.com/Gosha/5360a3109f77bc0defe7
See file_to_fhex() for an example of reading binary into hex pairs.
That example is simpler to read than tpdd_read() because reading from a file is simpler than reading from a stream wrt detecting eof.
read a byte, use printf to convert that byte to a hex pair. The funny looking syntax with a single ' in front of the variable is important.
See tpdd_write() for writing hex pairs back out to binary.
brace-expand "aa bb cc" to "\xaa\xbb\xcc", feed that to printf to output binary.
All through this script I'm using arrays instead of simple variables to hold the hex pairs. That's not necessary, I'm just doing that because this app needs to do a lot of byte/position counting on the data, both for parsing the output from the drive and for sending commands to the drive. It's all packets of fixed length fields.
I know full well that it is crammed full of inscrutabilities, relies on side effects, implicit behaviours, an ocean of globals & other state, and isn't even fully consistent with internal conventions.
This would be doing a bad job at work.
Some of that is just working with what you have, like the globals are how you avoid needing subshells so that's justified.
But there's other things like a lot of "business logic" is implimented in the form of arcane conditional brace expansions and particular return values of some commands etc, that could have been done more scrutably with ordinary readable explicit logic, just somewhat more verbosely.
I'm the first to say it aint readable. It's really a great example of "just because you CAN do something..."
But I wanted no dependencies (that aren't likely already there), cross platform, doesn't break in 6 months simply because 6 months have passed, yet interpreted vs a c program. Awk would tick the same boxes. I have both awk and ksh scripts I wrote in the 90's on xenix that still work the same today on linux & mac & bsd, meanwhile a python script breaks in a year.
As if the project wasn't enough of a challenge on it's own, let's throw in some russian roulette!
This must be someone younger than me.
Two crashes a couple months apart led me to switching to Linux. I then read up on rsync and using hard links to make incremental snapshot backups. Coupled with doing this over ssh and now I had multiple automated backups.
As my laptop got older, it kept running fine b/c of Linux but I wanted a new laptop. My girlfriend at the time was using said laptop and drinking and said "Oh, do you want me to not drink over your laptop?"
I replied "that laptop has every bit of important data I need backed up in two different locations. Please spill whatever you want on it as that would give me a valid reason to go buy a new one."
Debatable part of hacker culture. I'd say it's more a part of postmodern culture.
There’s a lot of furries too.
Might be confirmation bias, we see the people that stick out, but to say there isn’t a large cohort that strongly identifies with anime is just wrong.
Wrong part of the internet.
Speaking of, a burrito sounds great right now, so thanks for the reminder to go grab some dinner :)
Wow.
I guess the idea is that Bash is about piping together these small programs that typically take stdio and produce stdout. I think these have a name.. like GNU-style programs or something.
> Q: Why?
> A: Because I could. And it was fun!
I fed The Linux Documentation Project and Pure Bash Bible into my Anki decks which was really a turning point in writing bash for me.
It’s pretty amazing how much can be accomplished in a relatively small amount of bash.
Writing very simple scripts is certainly simple, but anything even a bit functionally more advanced (arrays, but even strings cycling or splitting is quirky) is a pain. Heck, even something as simple as process substitution is horribly unsafe (I wonder how many devs know how/why).
It's practically impossible to write correct Bash without shellcheck.
> It's practically impossible to write correct Bash without shellcheck.
I agree with this, since I've never seen a bash script, in the wild, that passed the important bits of shellcheck, without already being passed through spellcheck. I challenge anyone that thinks they've written good bash, with any significant level of complexity, to run that script through shellcheck. Each time I've done this, when starting at a new company, the response has usually been "Will not fix. That's why you don't use spaces in filenames/paths".
But it's not a developer's choice - Bash's syntax is cryptic itself. Here's a random sampling of common patterns:
Arrays:
- `${#myvar[@]}`: unreadable
- `mapfile myvar < <(grep pattern file)`: "mapfile" is not exactly a clear term (there's the synonym
"readarray", but "mapfile" is the reference; worse, you may find both used inconsistently); the
command as I wrote it also has two problems
Strings: - `${myvar##.*}`: unreadable
- `echo $(IFS=,; echo "${myvar[*]}")`: unreadable; most also don't know the difference between `[@]`
and `[\*]`; this doesn't also work for two-char join operations
- `[[ $myvar =~ $(echo '\bpattern\b') ]]`: unreadable, and most don't know why command substitution
is needed
Redirections: - `3>&2 2>&1 1>&3`: most don't know what this means
One can certainly get an idea of what's going on; but one gets an idea of what's going on also when reading assembly with labels.Neofetch is also written in bash: https://github.com/dylanaraps/neofetch/blob/master/neofetch
For my own work, I once wrote a functional file system based irc client in ~50 lines of bash: https://github.com/retrohacker/irc-sh/blob/master/main.sh
To learn bash:
This is an absolutely batshit crazy idea. I love it.
Sometimes its just fun to do something that's ridiculous. We're all talking about it here, aren't we?I also see that the author is having some trouble reading individual bytes. Perhaps these shell tricks might be useful.
At one point I've had a NBT parser implementation implemented almost fully, but I decided it was not worth the hassle to finish it. The code is currently lost, due to my extensive use of tmpfs as a project directory, and a system crash.
Hah, are you me? I also lost some Minecraft server management stuffs written in bash in a tmpfs dir. It was years ago, but I 100% feel your pain.Also, well done! This looks like it was a lot of fun.
https://gist.github.com/jaysoffian/e41ca479d70e60efe59fded93...
"psDoom" was great fun now i see "tunnel through the DB's page tables searching for a more direct route to the application engine"
Not exactly what you wanted, but Hajime can already query info from /proc to make it accessible from within Minecraft.
You can be a wizard in bash if nothing ever goes wrong. Functions calling functions calling functions. It’s basically programming.
Until something goes wrong. Then there’s nothing you can do.
Anyone who has ever set-dash-ee’d and then seen a function execute without set-dash-ee when used in an if context knows what I mean.
Or set -x if you're debugging.
Set -e is not global. It only applies in some cases. In other contexts (inside a function when called as if <function>) errors will not be caught and once you realise that you realise there’s no hope of writing error proof logic in bash.
I mean, while it's amazing what you can do with bash, I'd be wary to use it for "production" stuff. So far, I mostly used it for "toolbox" type if stuff that is only supposed to be used by the devs. For that purpose, it worked well though.
While I admire the skill involved, I keep wondering what drives people into starting such projects :D
These problems (yes, self-constrained) look like so much fun to solve, in a way that "I did xyz thing in pure CSS" is just less so to me.
Maybe It's that I miss rapid cycle of Completely Lost -> Earth-shattering Realization of How To Make This Work that the first ~1-2 years of my programming journey was full of
Contributions welcome :) Our wiki is open and anyone can edit. If you can ELI5 something to make it easier to understand, go for it.
And I was sad to see some things farmed out to awk. I mean what's next? bc? ed? ;-)
fao_@blob:~$ time $(echo '' | awk '{print (2*-1)}' >/dev/null)
real 0m0.006s user 0m0.006s sys 0m0.000s
fao_@blob:~$ time $(wcalc '2*-1' >/dev/null)
real 0m0.005s user 0m0.001s sys 0m0.005s
I'd imagine you could use something like xargs to insert the number so that wcalc can interpret the bits for you
Also: people wiriting things in bash usually try not to rely on utilities that are not present by default, and wcalc is not.
Title of my programming memoir right there