Skip to content

Instantly share code, notes, and snippets.

@JoshCheek
Created July 11, 2016 16:27
Show Gist options
  • Save JoshCheek/7b587da1952bd53395484daac5697432 to your computer and use it in GitHub Desktop.
Save JoshCheek/7b587da1952bd53395484daac5697432 to your computer and use it in GitHub Desktop.
Explanation of a confusing line of bash code / ANSI escape sequences.
echo "\[\033[${2:-37};60m\]${1}\[\033[0m\]"

Echo is the program echo that you can run from the terminal. (eg $ echo hello world) it works here because the output of the function is being captured and put into a string.


"\[\033[${2:-37};60m\]${1}\[\033[0m\]"

In Bash, pretty much everything is a String (not totally true, but true enough). So, unlike Ruby, the quotes in Bash arn't there to tell you it's a string, they're there to "quote" the value so that it doesn't wind up getting separated into multiple arguments. As an example echo hello "hello", both arguments are the same. And echo a b will print "a b", but echo "a b" will print "a b" because it's quoted.


\[\033[${2:-37};60m\]${1}\[\033[31m\]

At this point, I'm going to spit it up a bit so that it's easier to see which things are grouped together. Keep in mind that the spacing is not in the original. I'm also going to put a description over each chunk, so that you can reason about it as we get to the next pieces.


colour/underline the foreground   interpolate the first argument    reset styles (removes colour/underline)
\[\033[${2:-37};60m\]             ${1}                              \[\033[0m\]

Okay, so Unix only lets you send text back and forth, which means there's no way to send metadata. For us, data would be something like the git brnch, and metadata would be something like the font colour. Since we don't have a way to do that, the terminal looks for specific sequences of text that it will consider to be metadata. These characters don't have any width, because they're not printed, because they're not output. That's all well and good, but if Bash needs to calculate the width of the output, as it frequently does for a prompt, in order to put the cursor after it, then it will need to know which which characters actually show up in the output, in order to count them. It gets confused a lot, so what you can do is sort of clue it in by telling it "don't count any of these characters". The way to do that is by surrounding the nonprinting characters with brackets. However, you might want to print brackets, so how can it tell if you're printing brckets or instructing it to not count the characters? Well, they decided that if you're not printing them, then you should escape them with a backslash, like \[this\]. So, we wrap each nonprinting bit in escaped brackets, for us, that's the instructions to colour/underline, and to reset the style.


colour/underline the foreground   reset styles (removes colour/underline)
\033[${2:-37};60m                 \033[0m

See that one in the middle? Bash treats that like a fancy string interpolation, the 1 inside it means "the first argument to my function", so we're interpolating argument 1 right there. That's how the the actual text winds up in the output.


colour/underline the foreground   reset styles (removes colour/underline)
\033[${2:-37};60m                 \033[0m

Same thing with the ${2:-37}, we're interpolating argument 2, like ${2}, same as we did with argument 1. For us, argument 2 is the foreground colour, we passed it "32", when we called it, because 32 means to set the foreground to green. However, if we hadn't passed it, then there would be no argument 2, so the :-37 is saying "or, if there is no value, use the value of 37", which means the foreground should be white. For us, we have a value, 32, so ${2:-37} will become 32


colour foreground green and underline   reset styles (removes colour/underline)
\033[32;60m                             \033[0m

Okay, these two are starting to look really similar. They are called "ANSI Escape Sequences", they're the metadata we're printing to the terminal. Depending on what terminal you use, these could hypothetically change, but realistically they'll be pretty consistent. I mean, terminals are pieces of hardware, and no one uses them anymore since we all have fancy laptops, so people have written programs that emulate the hardware, in order for all of the programs to continue working. It's impressive in that things from forever ago still work, but it's also kind of stupid, b/c here you are learning this ridiculous escape sequence thing, instead of some reasonable API. Nonetheless, this is our reality, and the best way through is to accept that and just learn it.

So, we call it an "escape sequence" because it's a sequence of characters that begin with the escape character. The escape character is literally the character that is emitted when you press the escape key in the upper left of your keyboard. Normally you don't see it, because it doesn't print, and because most programs treat it as an instruction to the program rather than text to be entered. The ASCII value of escape is 27 (capital A, for comparison, is 65). And 27 in octal is 33. When you see a number that begins with a 0, most programming languages, including Bash, interpret that as octal. So 033 is octal 33, which has decimal value 27, which is the value of the escape key.

We put a slash on it to escape it, since we're actually in quotes right now (we removed them earlier). This tells Bash to treat it like a number insead of a series of characters. So it replaces that with the number 27, which is "escape".

After the escape is a left bracket, because... reasons.

Together, the escape followed by the left bracket is the "control sequence introducer" or some shit (it probably says in the wikipedia link below). And that's just how we tell the terminal that we're giving it an ANSI escape sequence instead of normal text. Otherwise it would print "32;60m" rather than turning the foreground green and underlining it.


colour foreground green and underline   reset styles (removes colour/underline)
32;60m                                  0m

Okay, so now we've got 2 ansi escape sequences. The thing to know about them is that the letter at the end is what matters most. It's almost like a function. It even takes arguments, that's what the numbers are. And if we need to give it more than one argument, we use a semicolon to delimit them. So "32;60m" might look like this in Ruby: m(32, 60).

Each letter has a different meaning (eg H lets you set the position of the cursor: echo -e "\033[10;20H \033[42;31m red on green in the middleish of your screen ") You can get a list of the characters at https://en.wikipedia.org/wiki/ANSI_escape_code, whenver they say "CSI", they're talking about the escape key followed by the left-bracket.

Our character is "m", and that means "Sets SGR parameters, including text color". And we go look in the SGR section, and see that "30–37" means "Set text color (foreground)", and described as "30 + n, where n is from the color table below". So we look in the colour table and see that 2 corresponds to green. So 32 means "foreground green"

Now we look up 60, and see that it means "ideogram underline or right side line", but that it's "hardly ever supported", so it might not actually do anything on your terminal.

And finally, we look up the 0, and see it means "Reset / Normal", described as "all attributes off", this essentially "we're done making things green and underlined now".


Now, go back to the original example, and try to break it down like the explanation above! If you get stuck, take a peek at the explanation. Keep doing it until you can break the entire sequence down without referencing the explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment