Bash Command Sequence – Using Variables Inside Commands

bashterminal

I'm using the following sequence of commands in my .bashrc file to alter the appearance of my linux terminal. It fills the screen line by line with a pattern made out of characters. There is no abstraction, the characters are from a set within the command itself:

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
                                                                       -->the set

The main idea is to read 80 cols (bytes) from some random characters from the set, and to print that * number of lines. Now, I've run the following contributed script in order to explore adding new characters to the set. To maintain compatibility with the linux terminal I'm using, I've run this outside of X etc, with the following result:

enter image description here

I'd like to to use the available characters in the sequence above. So I took many of them and did the following, for instance with ◘:

echo -n ◘ | hexdump
0000000 97e2 0098
0000003
so the UTF-8 sequence is \xE2\x97\x98
I build all the sequences I need: \xE2\x95\x99, \xE2\x95\x9a to f, \xE2\x96\x90 to 93

So I simply add to my .bashrc file A=$(echo -e '\xE2\x97\x98') and B=$(echo -e ',.o;:~') and I modify my command sequence like this (i.e. echo $A$B):

for i in $(seq 1 $(expr $(tput lines))); do echo -en '\E[0;32m'$(tr -dc $(echo $A$B) < /dev/urandom | head -c $(tput cols)); done; tput cup 1 

If I echo $A or $B at the prompt, it prints the char(s). But when the sequence is called in .bashrc this mostly doesn't work at all. On an entire screen ◘ appears 3-5 times total along with many placeholder chars meaning the output is not supported by the term. The other characters from the set are there. Of interest is that if I kept the original syntax with no $B variable and simply tried to add $A to the set i.e. tr -dc '",.o'$A';:~' I get the exact same sort of output, suggesting it's something else than syntax – because of /dev/urandom. Other variations on the syntax using quotations introduce more unrelated echo.

As a side note, in xterm, the result is similar with different placeholder chars, and a few ☗ and ◗.

Is there a way to bring the variable in the set like that or does this need to be redesigned from scratch to account for this case?

Best Answer

This is a non universal solution which achieves the intended result without exploring the difference between tr and the one in the heirloom toolchest or redesigning what I have for the moment. As such, it is flawed but pragmatic. As a contributor alluded in relation to tr, there is a restriction to the use of this command:

Currently tr fully supports only single-byte characters. Eventually it will support multibyte characters; when it does, the -C option will cause it to complement the set of characters, whereas -c will cause it to complement the set of values. This distinction will matter only when some values are not characters, and this is possible only in locales using multibyte encodings when the input contains encoding errors.

So actually only 8bit 1 byte chars (non Unicode) like the ones in my initial set are supported through my sequence. I also have a constraint that I'm rendering one screen worth of characters and I don't want more so the idea of adding some other randomness for the new chars was less appealing i.e. how to control total chars written. So I decided to post process the output. The scope of available characters to be used as a pattern will be smaller than the total 1 byte chars available so I can use those I never intended to use in the first place and repurpose them as "variables" like this:

Z1=$(echo -en '\xe2\x97\x98')  \\ Z1 to Z9 will be used to
Z2=$(echo -en '\xe2\x95\x9a')  \\ bring in our non unicode
Z3=$(echo -en '\xe2\x95\x9c')  \\ chars
Z4=$(echo -en '\xe2\x95\x9d')
Z5=$(echo -en '\xe2\x95\x9e')
Z6=$(echo -en '\xe2\x95\x9f')
Z7=$(echo -en '\xe2\x96\x91')
Z8=$(echo -en '\xe2\x96\x92')
Z9=$(echo -en '\xe2\x96\x93')
Z11="$(tr -dc '123456789a' < /dev/urandom | head -c 1)"  \\Z11 to Z13 used to
Z12="$(tr -dc '123456789a' < /dev/urandom | head -c 1)"  \\reintroduce the chars
Z13="$(tr -dc '123456789a' < /dev/urandom | head -c 1)"  \\used as variables

tput setf 6
tput setaf 6

echo -en $(tr -dc '",.o;:~123456789a' < /dev/urandom | head -c $(echo -en "$[$(tput cols) * $(tput lines)]") | sed -e "s/1/$Z1/g" -e "s/2/$Z2/g" -e "s/3/$Z3/g" -e "s/4/$Z4/g" -e "s/5/$Z5/g" -e "s/6/$Z6/g" -e "s/7/$Z7/g" -e "s/8/$Z8/g" -e "s/9/$Z9/g" -e "0,/a/s//$Z11/" -e "0,/a/s//$Z12/" -e "s/a/$Z13/g"); tput cup 1
                   ^set  ^^vars    ^                                                                              "variables" ouput are replaced with intended char>___________________________________________________________________________________________^...then vars themselves reintroduced here as chars, one 'a' at a time(only 3 shown here, last one is global)
                                  |__________________________________________________________________________________|

The updated sequence is slightly different than in the Q and is simply rendered in one clean sweep instead of line per line. It uses sed and its ability to accept many commands to apply to the stream with -e. The numbers 1 to 9 and the letter "a" are randomized into the full page pattern. The output pattern is then filtered with sed and 1 to 9 (if present in the random stream) are all converted into our intended non unicode chars - only a is remains. Those "a" are then processed and used to reintroduce the chars 1 to 9 and a itself in the pattern using variables Z11 to Z13 (random generator with head 1 byte that uses 1-9 and "a" as a set now that we don't need them anymore). Each instance of a could be individually randomized (see pattern sample below) or left as is (another char than "a" can be chosen of course, one that we might enjoy seeing in the final pattern). This proof of concept is incomplete as you would ultimately need to intercept all the a chars in this example and transform them into something really random. But it works. This is just to show these chars (and I would select in practice chars that I don't want to use in the pattern so there would be no need to reintroduce them like this in the first place) can be reintroduced. The tr obstacle has been "avoided".

enter image description here

We can see what happened to the a's - the first one was converted to 8 and the second one to 1, whereas the rest were converted to 4s (sed global). 1,4 and 8 were all chars used to bring in our special chars, and now they can be used too if need be. No doubt an elegant solution exists but this is not one of those. This is all generated in the blink of an eye.

Here's a simplified version formatted like a script which uses digits 1-9 for our pattern without 'redeeming' them like explained above:

#!/bin/bash

## spptr_dfp.sh - Display a chosen pattern of chars using tr, while using sed "post processors" to deal with
## multibyte chars that tr can't stomach. Rely on single chars not used for the pattern to bring in the
## multibyte ones later on in the pipeline.

## Z1 to Z9 will be used as primitives variables
## to bring in our non unicode chars
Z1=$(echo -en '\xe2\x97\x98')
Z2=$(echo -en '\xe2\x95\x9a')
Z3=$(echo -en '\xe2\x95\x9c')  
Z4=$(echo -en '\xe2\x95\x9d')
Z5=$(echo -en '\xe2\x95\x9e')
Z6=$(echo -en '\xe2\x95\x9f')
Z7=$(echo -en '\xe2\x96\x91')
Z8=$(echo -en '\xe2\x96\x92')
Z9=$(echo -en '\xe2\x96\x93')

## the color we want
tput setf 6
tput setaf 6

## the main event, generated for cols*lines
## the pattern is made out of ",.o;:~
## the single chars used as vars are 123456789
## after tr outputs, sed converts any of the single chars to our multibyte chars
echo -en $(tr -dc '",.o;:~123456789' < /dev/urandom | head -c $(echo -en "$[$(tput cols) * $(tput lines)]") | sed -e "s/1/$Z1/g" -e "s/2/$Z2/g" -e "s/3/$Z3/g" -e "s/4/$Z4/g" -e "s/5/$Z5/g" -e "s/6/$Z6/g" -e "s/7/$Z7/g" -e "s/8/$Z8/g" -e "s/9/$Z9/g")

tput cup 1

exit
Related Question