CSI: Echo, Make, and ANSI
Earth, Grass, and CSI
I recently heard about the EGOS project and, being interested in Operating Systems, RISC-V, and systems and embedded programming, naturally, I wanted to check it out.
The project revolves around understanding EGOS-2000: an Operating System designed for teaching about Operating Systems, with only ~2000 lines of code. The E stands for the Earth layer, which involves abstracting away the RISC-V ISA, while the G signifies the Grass layer, or hardware-independent abstractions, upon which the remaineder of the OS is built.
I'll leave it at that for now, but hope to post more about the project in the near future. If you're interested, please do check out the link above.
Make it Work
More to the point of this post: I had a small struggle to the get the project up and running on my system. I had to set up QEMU on Linux for starters, which ultimately was not terribly difficult, but is not as simple as, say, grabbing a singular "qemu" package. The darn thing wouldn't compile either, but again, I'll save that for later...
The Makefile worked fine. Being an Arch user, and as most, already having the base-devel package (which is pretty much essential for installing/building packages from the AUR), I of course had the GNU make
utility already installed. As a developer, I'm no stranger to Makefiles, but in the course of my particular career path, it hasn't been often that I've needed to deal with them, much less create or edit them (though I've been learning more about them due to their popularity with the Golang crowd).
That being said, it didn't work fine. Yes, it ran the specified commands, but the output was a mess. Let me illustrate:
;
;
;
;
;
;
;
;
;
;
;
;
;
\0;
Notice anything odd in the output? Yeah, the little \033[1;36m
and similar entries, spread a couple times each on multiple lines in the output. Almost immediately, I recognized what I was seeing, since I have the same type of entries in my Bash prompt:
RED="\[\033[0;31m\]"
YELLOW="\[\033[0;33m\]"
GREEN="\[\033[0;32m\]"
WHITE="\[\033[1;37m\]"
GRAY="\[\033[0;37m\]"
NONE="\[\033[0m\]"
BG_GRAY=
BG_CYAN=
reset=
cyan=
white=
gray=
magenta=
git_status="\$(__git_ps1 '\[ \][%s]\[ \]' )"
tmsp="\[ \] \t \[ \]"
corner_top="\[ \]╭\[ \]"
row_top="\[ \]-{\[ \]"
corner_bottom="\[ \]\[ \]╰{\[ \]"
row_bottom="\[ \]%\[ \]"
ps1_top=" \w "
ps1_bottom=" "
PS1="\r\n \r\n"
Here's how it looks:

(Yes, that is quite a PS1 and the code snippet doesn't even include the Git-checking bit, but is it any worse than Starship!? Also, you may notice the use of tput
in place of those cryptic entries I mentioned; this is a GNU utility that essentially looks up a terminal capability and translates it to your specific terminal emulator -- the setaf
code essentially means, "set ANSI foreground". See man terminfo for more).
That's right, you guessed it: ANSI escape codes! Specifically, the \033[
equates to ESC [
, which is an ANSI Control Sequence Introducer. The values between there and the letter m
basically tell the terminal to print the characters that follow in bold or in a certain color. Another magical string tells it to stop doing all that and go back to normal -- kinda like wrapping your webpage text in a <span>
with attributes or CSS to change the look of the text between. In this case, the 1
means bold, and the 36
is a color code. Select Graphics Rendition covers this font stuff and other display changing abilities, including blink, strikethrough, background color, etc.
Echo-o-o-o-o-o
Now that we know what we're dealing with, why the heck does it work in my PS1 prompt but not when I run the make
command? I experienced the same whether using tmux or no, Alacritty (Bash), or Ghostty (Bash).
I confirmed that issuing printf
and even echo -e
commands would properly interpret the escape sequences, but running the make
command or a plain echo
always resulted in the escape sequences printed out instead of interpreted.
Well, it boils down to that echo
command, in my case (and likely yours), supplied by GNU Echo in the coreutils
package (which for me seems to exist at both /usr/bin/echo
and /bin/echo
-- without symbolic link). Apparently, some shells even have their own versions of the command. In any case, whatever is echoing text may or may not interpret those escape codes, depending on how it's configured. Given all the variables at play, it can actually be quite complicated, especially when you figure in cross-compatibility between Linux, BSD, MacOS, and Windows!
Use tput
you say? This is a good cross-terminal solution, looking up the correct ANSI interpretations via terminfo
, but not for Windows users. What about that echo -e
I mentioned? There doesn't seem to be a consensus on which systems support that flag, which might mean it fixes the problem, is totally ignored, or completely breaks the Makefile.
I did some research and I discovered a couple of things:
- For one, enabling the Shell Option
shopt -s xpg_echo
solves the problem, if your environment supports it. This enables the parsing of those escape sequences. - Secondly, using
printf
is the preferred cross-platform solution.
So, there you have it.
And I'm proud to say, I didn't keep this information to myself. Not only am I sharing it here with you today, but I also updated the Makefile and submitted a pull request to the EGOS project, which was accepted by the maintainer (who also thanked me for the thorough explain -- yes, I know I am a bit wordy).
Hope it helps someone if they happen across this one day.