Context and Question
There are many ways to colorize the terminal and shell environment. The output of individual commands, such as ls
and grep
, can also be colorized. Not directly related but interesting nonetheless is the notion of playing media at the console but this seemingly relies on some framework (libraries) on top of the windowing system. The following question is geared solely at the bash
shell and its implementation in the Linux terminal framework and its underpinnings.
Please consider the following montage of ASCII "renderings" of a scene in a 2D game:
These are not randomly generated scenes. The segments I've selected all actually depict some form of "grassland" terrain (trees, bushes&shrubs, flowers, grass etc.) from a game that uses ASCII characters to represent such objects. The last 4 scenes showcase user made tilesets which are basically a remap of ASCII characters with color specs (such details are trivial - suffice it to say that this is the visual inspiration for what I'm trying to accomplish here in terms of visuals and "pattern").
The common features those scenes in the montage share are:
- 5-6 different ASCII characters at most (commas, quotations marks and a few others)
- 2-4 colors used
- for the characters
- for the character backgrounds in some cases - the last example is there to show the use of color shades with little or no character to create a pattern i.e. color mosaic
What I have in a VM at the moment is Arch Linux and although the question is not distribution specific, I've looked into their documentation for customizing the /etc/bash.bashrc
file. I can see that lots of explaining go into configuring the appearance of the prompt and generally all the foreground elements. There is little information on any configuration for the background, except usually for a solid color, such as these settings and tips:
# Background
On_Black='\e[40m' # Black
On_Red='\e[41m' # Red
On_Green='\e[42m' # Green
On_Yellow='\e[43m' # Yellow
On_Blue='\e[44m' # Blue
On_Purple='\e[45m' # Purple
On_Cyan='\e[46m' # Cyan
On_White='\e[47m' # White
I still don't conceptually grasp what are those empty/blank/background "spaces" that I didn't type when I use the console i.e. "what are they made of?" so to speak. Especially those that are not at the prompt, and which wrap around the commands that are echoed. In respect to what happens on the active line, it is possible to demonstrate that bash
acts in a "line-oriented" way and that some operations trigger a clearing of the active line (for i in $(seq 1 $(expr $(tput lines) \* $(tput cols))); do echo -n M; done; tput cup 15 1
, then at the prompt type a char and backspace it - demonstrated a contributor) - the extent of which may vary from a CLI to another i.e. zsh. Furthermore, it seems when I add something like \[\033[44m\]
to my PS1 line in bash.bashrc
I get a blue background after reloading bash - so obviously I know that there is some leverage here over of the output appearance insofar as the background is concerned.
But I also know that bash is a piece of software relying on some other facility in the form of the TTY subsystem for bringing things to the screen - and this goes down from there to the VT component in the kernel I assume. pstree -Ap
on Arch shows systemd
linked to login
and then to bash
.
The Arch Linux distribution relies on agetty
for TTY services. A simple echo $TERM
will yield the type of terminal in use ("linux" here outside of any DE) and the infocmp[-d spec1 spec2]
command with no parameter shows the active terminal capabilities and profile information from the terminfo(5) terminal database:
# Reconstructed via infocmp from file: /usr/share/terminfo/l/linux
linux|linux console,
am, bce, ccc, eo, mir, msgr, xenl, xon,
colors#8, it#8, ncv#18, pairs#64,
acsc=+\020\,\021-\030.^Y0\333'\004a\261f\370g\361h\260i\316j\331k\277l\332m\300n\305o~p\304q\304r\304s_t\303u\264v\301w\302x\263y\363z\362{\343|\330}\234~\376,
bel=^G, blink=\E[5m, bold=\E[1m, civis=\E[?25l\E[?1c,
clear=\E[H\E[J, cnorm=\E[?25h\E[?0c, cr=^M,
csr=\E[%i%p1%d;%p2%dr, cub=\E[%p1%dD, cub1=^H,
cud=\E[%p1%dB, cud1=^J, cuf=\E[%p1%dC, cuf1=\E[C,
cup=\E[%i%p1%d;%p2%dH, cuu=\E[%p1%dA, cuu1=\E[A,
cvvis=\E[?25h\E[?8c, dch=\E[%p1%dP, dch1=\E[P, dim=\E[2m,
dl=\E[%p1%dM, dl1=\E[M, ech=\E[%p1%dX, ed=\E[J, el=\E[K,
el1=\E[1K, flash=\E[?5h\E[?5l$, home=\E[H,
hpa=\E[%i%p1%dG, ht=^I, hts=\EH, ich=\E[%p1%d@, ich1=\E[@,
il=\E[%p1%dL, il1=\E[L, ind=^J,
initc=\E]P%p1%x%p2%{255}%*%{1000}%/%02x%p3%{255}%*%{1000}%/%02x%p4%{255}%*%{1000}%/%02x,
kb2=\E[G, kbs=\177, kcbt=\E[Z, kcub1=\E[D, kcud1=\E[B,
kcuf1=\E[C, kcuu1=\E[A, kdch1=\E[3~, kend=\E[4~, kf1=\E[[A,
kf10=\E[21~, kf11=\E[23~, kf12=\E[24~, kf13=\E[25~,
kf14=\E[26~, kf15=\E[28~, kf16=\E[29~, kf17=\E[31~,
kf18=\E[32~, kf19=\E[33~, kf2=\E[[B, kf20=\E[34~,
kf3=\E[[C, kf4=\E[[D, kf5=\E[[E, kf6=\E[17~, kf7=\E[18~,
kf8=\E[19~, kf9=\E[20~, khome=\E[1~, kich1=\E[2~,
kmous=\E[M, knp=\E[6~, kpp=\E[5~, kspd=^Z, nel=^M^J, oc=\E]R,
op=\E[39;49m, rc=\E8, rev=\E[7m, ri=\EM, rmacs=\E[10m,
rmam=\E[?7l, rmir=\E[4l, rmpch=\E[10m, rmso=\E[27m,
rmul=\E[24m, rs1=\Ec\E]R, sc=\E7, setab=\E[4%p1%dm,
setaf=\E[3%p1%dm,
sgr=\E[0;10%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p5%t;2%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;11%;m,
sgr0=\E[0;10m, smacs=\E[11m, smam=\E[?7h, smir=\E[4h,
smpch=\E[11m, smso=\E[7m, smul=\E[4m, tbc=\E[3g,
u6=\E[%i%d;%dR, u7=\E[6n, u8=\E[?6c, u9=\E[c,
vpa=\E[%i%p1%dd,
As it stands, many capabilities can be leveraged from the terminal framework and it is basically those features which are exposed in the bash.bashrc configuration file insofar as the prompt is customized by setting the PS1 variable. Control and escape sequences are used to basically interrupt the flow of character displaying in the terminal in order to supply functions, including moving the cursor and other capabilities described in the terminal information database. Many of those functions are passed in using the well known ESC[
(or \33) Control Sequence Introducer (more sequences here and here, and some examples). Furthermore, it is also possible to use the tput
utility directly on the CLI to change some terminal properties; for instance tput setab 4
will have bash echo commands on a blue background.
If we strace bash
we can see both the escape sequences and behavior in action:
write(2, "[il@Arch64vm1 ~]$ ", 19[il@Arch64vm1 ~]$ ) = 19 //bash starts
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, " ", 1) = 1 //pressed <space>
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
write(2, " ", 1 ) = 1
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "\177", 1) = 1 //pressed <backspace>...
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
write(2, "\10\33[K", ) = 4 //triggers erasing the line
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
read(0, "\33", 1) = 1 //pressed <esc> per se
This provides context for the question Can the empty spaces/background color in a terminal be replaced with a random(but pretty) set of ASCII characters? but gives no idea of how to implement the features or of what I'm looking for in the terminal.
So I've created a crude mockup as an example of what the end result could look like if this were possible (no seriously:):
Basically all the "empty space" in the terminal would be filled with the pattern(here I "tile" one of the image from above but I would like in the actual implementation for each individual "blank" to be generated randomly from the set of 5-6 characters and features documented from the montage which would be specified). There is a different pattern for the active command line i.e. wavy "water" but I'd settle for the line being blue. As this was imagined, commands would "erase" the "water" as they get typed on the active line and of course a constraint would be that the pattern of characters never gets interpreted by the CLI otherwise it would make it useless.
So is there any configuration exposed in bash
or in the terminal framework proper, or a script which would allow to use a set of characters and some control over colors to modify the output of bash in terminal so as to generate a somewhat random pattern for the background (which would be similar to what I've shown above)? Or should I simply settle for something like trying to supply a full pattern image as a background for the tty?
Implementations
0.1 - PatternOTD version(one shot deal when you login)
The following expression I added to my .bashrc file puts together some of the notions we explored and constitutes a (very)basic proof of concept for the visuals in the standard linux terminal:
for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[32;32m'$(tr -dc '",.;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 15; tput setab 4; echo -en "\E[2K"; tput setab 0
Observations
- It's obviously just a command so not persistent i.e. it scrolls away as commands get typed
- Opted to not individually randomize the selection of characters i.e.
head -c 1
with thetput cols
multiplying the lines to begin with, which would print individual random chars from the quoted selection - because it's too slow. I don't thinkrandom
generates a (tput cols) long integer but still it's faster. Surely this is all very wasteful but it works. - Haven't randomized any color or effect per character or otherwise except for that green because as I explained rendering/processing each char individually is too slow. Re: framebuffer?
- I'm happy to see that the pattern doesn't interfere with CLI usage in the sense of not being interpreted by the CLI! (why though I couldn't explain)
- Water is gone too fast! ;-)
0.2 - PROMPT_COMMAND hack job
The value of the variable PROMPT_COMMAND is examined just before Bash prints each primary prompt. I know usually you would use the variable to call a script where you could process elements from the display etc. but I'm rather trying to do this directly in my .bashrc file. Initially I thought I could implement some positional awareness i.e. where is the cursor before execution (so I could go render things on screen anywhere with tput
then come back to the position I was before, using something like this to extract position:
stty -echo; echo -n $'\e[6n'; read -d R x; stty echo; echo ${x#??} //value is in x;x format so...
I would pipe the value to cut -f1 -d";"
. I can do this on the CLI but making this work inside the sequence of elements in the PS1/P_C variables is out of my reach at the moment and it's possible that whatever command is put in PROMPT_COMMAND may not be evaluated at every carriage return but rather only once(?) despite being executed every time (see observations below).
So the best I could do is carry over my initial sequence and add some commands to both PROMPT_COMMAND and the definition of the PS1 variable in .bashrc. Like so:
PROMPT_COMMAND="echo -en '\E[32;32m'$(tr -dc ',.:~' < /dev/urandom | head -c $(echo "$[$(tput cols) * 2]"))"
PS1="$(echo -en '\n') $(tput setab 4)$(echo -en "\E[2K")$(tput setab 0)\[\033[7;32m\]df:\[\033[1;34m\] \W @d \[\033[0m\]\e[32m"
for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[32;32m'$(tr -dc '",.;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 1; tput setab 4; echo -en "\E[2K"; tput setab 0
In summary I'm using P_C to try to implement a persisting visual pattern i.e. 2 lines get added. Unfortunately I cannot succeed at creating both this pattern while repeating my "water" trick i.e. having the active line blue (which is just changing the background color, doing a clear line, then changing the background back to black). I've put together an image to show how this plays together:
Observations
- Using backspace on a line still triggers the clear line behavior and the blue is gone
- Each time the enter key is pressed we have 2 lines of pattern before the new active line
- Of course as we see further down despite the extra lines we're not wrapping the pattern on the side of the commands such as
ls
- /dev/urandom 's randomness seems not so random when called here in P_C. This image is made out of 2 images, but it's easy to pick up that the extra 2 lines pattern is always the same i.e. the randomness is not generated with every enter keypress but only one time for each of the two lines - possibly only the first time .bashrc is read by
bash
. - The content of the PS1 variable starts with
$(echo -en '\n') $(tput setab 4)
- well that space in the middle there, just before $(tput ...), it MUST be there for this to work. Otherwise the blue line appears on top of the prompt and not in front of it and I can't resolve that. And this hack is what gives its name to 0.2. :)
0.3 - tput cuu
& tput cud
for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[0;32m'$(tr -dc '",.o;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 1
PROMPT_COMMAND="echo -en '\033[0;32m$(tr -dc ',;o.:~' < /dev/urandom | head -c $(tput cols))\n\033[36;44m$(tr -dc '~' < /dev/urandom | head -c $(tput cols))\033[0;32m$(tr -dc ',.o+;:~' < /dev/urandom | head -c $(tput cols))'$(tput cuu 2)"
PS1="\[\033[0m\] \[\033[1;32m\][1]\[\033[7;32m\]=2=:\W)\[\033[0;32m\]=3=\[\033[1;32m\]=4=@>\[\033[0;32m\]"
What is done with PROMPT_COMMAND is that 3 lines of patterns are printed every time before the prompt is generated - and those 3 sets of patterns are individually generated within the constraints explained in 0.2 - meaningless for the water as it's 1 char but still. Then we go up two lines (using tput cuu 2
) and the prompt is generated on the middle line according to PS1. We still have our initial set of command for the full screen pattern on .bashrc load which is executed only once when we log in to the terminal. Now we have some padding around the active line which has its own blue pattern which always gets repeated when there's a return carriage. The contents of the PS1 variable and the P_C have been sanitized. The syntax of the escape sequences and color coding embedded within long echo
sequences can be tricky. Errors lead to weird terminal behavior including lines that overwrite each other, a prompt that appears away from the left margin or unusual output to stuff that has been processed unintentionally. A condition exists with what I'm doing, where an extra space is required inside the PS1 variable to counter a visual difference between the linux terminal and lxterm with my setup (Arch Bang). Without the extra space, the linux terminal prints the first character of the prompt at the end of the last line for some reason I can't figure out (of course it's something that I do and not the default behavior). Also can't figure out how to generate some random effect (bold, inverse etc) to the set of chars in quotation marks, as it was decided early on to generate longer strings to increase performance. There is still no real persistence of the pattern outside the padding around the prompt, but at least there's the water and it's simpler than before and somewhat more predictable.
Initial pattern when the terminal opens
Behavior after a clear
and pressing enter successively at the prompt
Observations
- Should be redesigned or modified to implement colorizing the patterns beyond doing it in bulk
- Starting to feel that going much further will require either putting all that into a script or leverage some higher form of abstraction. But the terminal capabilities are quite enabling for the end user (reminds me of "logo")!
strace
on bash but you must be talking about bash source? Yet in the trace I recognized some stuff i.e. when I typewrite(2, "[root@Arch blabla]# \33[44m " , 28[root@Arch blabla]# )=28
I see the reference to "blue" - and also, if I type a char, then backspace, I seewrite(2, "\10\33[K", )
- that 33 seems somewhat familiar. All of this is a very long shot for me - I need to read more!\33
is ESC, the escape character (decimal 27, hex \x1f). The sequenceESC[
is 7-bit CSI (8-bit CSI is\x9f
), the Control Sequence Introducer, which introduces many control sequences. In particular,CSI K
is Erase in Line (EL, clr_eol), which by default erases from the current position to the end of the line.CSI 1 K
erases to the left, andCSI 2K
erases the whole line. You may want to skim intoterminfo(5)
,console_codes(4)
, and/or/usr/share/doc/xterm-*/ctlseqs.*
– ninjalj Dec 17 '13 at 02:14infocmp -d linux vt100
. I notice the reference toel=\E[K
i.e erase but I struggle with such grammar assetab=\E[4%p1%dm
lol. I can also usetput
to change many terminal properties which your reference to terminfo(5) introduced me to, includingtput setab 4
to change the background color. Will continue reading. Your comments are very helpful. – Dec 17 '13 at 07:57for i in $(seq 1 $(expr $(tput lines) \* $(tput cols))); do echo -n M; done; tput cup 15 1
, then at the prompt type a char and backspace it. – ninjalj Dec 17 '13 at 12:05for i in $(seq 1 $(expr $(tput lines) \* $(tput cols))); do echo -en '\E[32;32m'"\033[1m\u2663""\033[1m\u10a5" "\033[2m\u22a7" "\033[7m\u26ab\033[0m"; done; tput cup 15; tput setab 4; echo -en "\E[2K"; tput setab 0
in terminal emulation loll. Lots of work to do, thanks again for the tips! – Dec 18 '13 at 08:09for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[32;32m'$(tr -dc '",.;:~' < /dev/urandom | head -c $(tput cols)); done; tput cup 15; tput setab 4; echo -en "\E[2K"; tput setab 0
works in the standard linux terminal as a concept :) – Dec 18 '13 at 11:12