QZ qz thoughts
a blog from Eli the Bearded
Tag search results for code Page 1 of 2

ASCII art and "ASCII" art


I first had email, and first saw a :-) smiley face in 1990. It was about that time, also, that I was exposed to concrete poetry. Both the smiley and the pictures of words poetry were influential on my mind.

I've had a long lasting love of ASCII art and things near to it since. The first program I saw that could create ASCII art from another image was pbmtoascii from the netpnm package. It worked, but it was kinda disappointing. I had seen, and even made myself, hand drawn ASCII art that was much better than the output of that tool.

$ pnmscale -w 60 < fine.pgm |pgmtopbm |pnminvert |pbmtoascii
 ooo" M o"o" oMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMo"
M oo"o" M o" MMMMM"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM "
o"o M "o M "oMMMMM"oM "MoM oMMMMMoo""MMM"ooM"o""" oMMMMMMM "
o" " M o"o"  MMMMMMMoMoMoMMoMMMMMoMMoMMMMoMMMoMMoM"MMMMMMM "
o""oo o o o"o""MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM o"
o"M M M"oM M "o""MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"o"o"
"o"" "" o"" ""o"ooo""""M"M"MMMMMMMMMMMMMMMMMMMMMMM""" o"o"oM
MM"MMMMMoo"M"oo ooo"oooMoMoooooo MMM M"o"oo" ooooo"o"M o ooo
MMMM"MM oM"oM"oMoM""MMMMM"MMM"MM MM MMMMMMMMMooooooooooMMM o
""MMMMM Mo"" "   o"M MoMMMMMMMMo"MM MMMoMMM"MM"MM"MM"MMMo"oM
 MM"MM" o     o MoM"o"M"MMoMMMM MM"MMM"MMoMMMMMMMMMMM"MM"o"M
 MMM o"Mo"Mo"o"M Mo" M""MMMMMoM M"oMMMMM"MMMo"MoMMoMMM"o"M"M
 M"o"o"oM" """" "ooM"oMo"MoMMMM"oMMMMM"MMMMM"o"M"MMM""oMMM"M
o"oM"oMo "M"M"M"Mo"oM"o"ooM""MooMMMoMMMMMoM"oM MMM"o"Mo"o"Mo
M Mo"M" "M"oM oMoo""o"oMM  " "MMMM"MMM"MMM"oMM MM MM"Mo"M"oM
"MoM"   o"M MMM"""MM MM"     "M"MMMMM""""M"M"Mo"oM"M"oMoM"Mo
"Mo"     "oMMM     "oMMo    o MM"""       "M MoM"M"oMMooMM"M
"M    o  M MMM      "oMo "   o"oM     "    MM MM"oMMo"MoMM"M
M        "o"MMo    o M M""" M"Mo     o    M"MoM"oMMoMMMoM"M"
o   "    ""o""Mo"o o"M"oMM"M"oMoo o     o MMMoM MMo"MoMM"M"M
         M"M"oooMo"M M"oM M"oMo"ooo oo"M MMoMM"MMoMM"MoM"MoM
o "  "   M"o"M M M "oo" M"oMo"oMo"oM"o"o"MoM"M"MoMMMMMoM"MoM
M       M"M M"oMoo" M "oo " ""o M oooMM"MMo"MMM"MoMoMM"M"MoM
o Moo"o"M M"o"oM M"o" o   "o"MMM"oMoMoMMMo"MMoMM"o"" """"o""
 "oo" M"o"M M"o"oM Mo"oo oo"o"Mo"MooMMoM"M"MoM"Moo""""" " M
o"Mo "o" M "o"o"o"o"oM M oo"Mo" MM MoMM"M"Mo"M"Mo"MMoMoMMMM
 MM "M M M M M M M"o"o"oM M "oMo"Mo"MoMM"M"oMM"Mo"M"M"MoMMM
"oMo""M "o M M M M M M"o"o"o"oM"Mo"MMo"MM"M"oMMMo""MMM"MoMM
"oM "M M Mo M Mo"oM M M M Mo "Mo"oMMoM"MoMM"M "oo"""o"""o"o"
"o"o"oMo M M M"oM M"o"oM Mo" MM"M"oM"M"Mo" oo"M M""Mo"M"oM M

Eventually I learned there is a higher quality option to the program.

$ pnmscale -w 120 < fine.pgm |pgmtopbm |pnminvert |pbmtoascii -2x4
//,|>|i:(<>/|,HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM?`S
SSS?!:?</:?? MMMMH##MMMMMMMHMMMMMHMHMMMMMMMMMMMMMMMMMMMMMH`S
??|!|::?://S!MMMMM}{&|'RTk:=MMMMM|?>dMMMk|qHT|9:?/:MMMMMMM S
::\\>\\\:i|>`HMMMM6HHJbM|Md?MMMMHJ6$dMMMkdMRJLHd?P&M]MMMM}i:
SSS>\:\,,:<??:HMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMH,\:
SS|$?S4?(L||/S>/*MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM",}$\
\\</|+`:?:`^`<??>,??"*##HHHHMMMMMMMMMMMMMMMMMMMMHH*"?:,/($([
RH&HHHH6'<&ST&v:$???$vd&pbqdoooo.MMP$$$?$$??::<v>\\\%%>/.vv,
M9MHH6R|S}&\S1%$?H*/H9H9RM9MH6M&:MM:HHH6H6H6H#\b????vo#H&M!?
<9&6M9M:F1>?^'''.<$[\9R6MHHHDM&}|MH:MRHRM6RMH9R6H6MHH&H9H/?k
|HHHHH/\:.    ./$S1S|JRHMHH6M9M!HM\M&RMHHHH&H6MHHHHHRMH&P|&H
:&M$?\$$$Z$}1?$S$>$?v??#RMMHHMH.H\HRMHH&RM9M9T&RM9M&M&F?/Z&&
:H?&|;4S%\??<^????>$&$S+:#HMHHP|?MH6M9RM96M&P??&6HHM>?d&1&$k
|&iD?D#d:&)&ZZ$\?F1%F$$$$?Z?HModH6MH9MHR6M&R|H/H6M$?S&H$|19|
&:/S&]>-?$S$>???$?<$Z+dM*"`"\?MHHHHHHHHHMH$d9&|9*>&$H&?${&$9
HFS$/-  :$S\MMM*"*Hb`HMT .- -`MM9M9RM**+#bR&$&L\{S6H>b$L$&kR
$Fk? .` :Z|MMR- .  ?|&M: .. -:Md#>*'  . .``H?&d1H&>b&kH+RH$&
DZ' . .  &:MM|.- ` .??Mc: ., $v$/-  `. .. .&9)DH$?HH&1&FkM&&
R  - . . |\?MM\ : -.\<?$PP??{47Z- . - .  .d&1c>$$9HH&1&FkkHZ
: .- -   |S}?*&qv\`,&Z/S$$$Z$k&S:   .   >,H&1&k?6&9H&H&S&$9{
 -. `.`  %&4%F>><1Z/|%$$$?FS$$Z/S\;_\v\&\/RH&H$HH&&DR66&&9S&
:- - . `.9%&4SFS$?!?o:<?$$\%Tk$S1%Z$$>::&6kM&FHH&RH&RH19&$9&
b.- -  /7/Z|$7/$?://?$-\b:?/'!???:?v/&M&&kH\6&HH&19H1&&19&19
//?+>|\$$%}S1?$S|k%|//:   '|?HRMP/FPd9&RH17H&&&FR/+^$???$$?\
:S$|S:%Z>}S$?($??|}Z\\\/,v$>??HT/&ZS&&9&1H?M%HH&|???"":`""?:
!ZF$ /||:S:(|||\\\4|?%|$S;v?&1;:H&>&FDFRH$1}&FkR|&9&H&#9&RM:
i$&||)S/?(|))))))?S??S4|%Z/|Z}&/&7/FRHFDF&k$RH&&|:M&DHHH&RH-
i&$||S}!%&:?SS?((((((((|$$?<:$$FR%FFk&9&S$&H$$R6k|9HFRH9&&M.
|&?||SF>|%Z:$S?SSSS?\\\??\\\`&$$|&&&&ZH&19?&&<???:?+?\<+*++|
|Z$\:S$$ $Z:\(}S4%$?L$$)?Z%||&ZH&&&&Z&&&1^`v>T/Z\\$&)F$$$14|

But that still seemed lack luster to me. About 21 years ago, the date I have in the file is 27 Dec 1999, I wrote my own tool that turns gray scale ("pgm") images instead of bitmap ("pbm") images into ASCII art.

pnmscale -w 60 -h 30 < /tmp/fine.pgm | pgmtoascii
zzxxxzmmzzmzzxOWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWmxz
mmmmmzzzzzzz;WWWWMOOWWWWWWMMWWWWWWWMWWWWWWWWWWWWWWWWWWWWWM,z
mmmmzzzzzzzzxWWWWWYYUmxOYCxXWWWWMXXzUWWWUzUOUXUmYzzWWWWWWM,z
zxzzzzzzzzxz,MWWWWOMOUYMYWUXWWWWOYOUUWWWCUWOUUOUXYUWOWWWWY+z
mmmzx++;++xzmxOWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWO+z+
XXXYYYYYYXmmzmxzUWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWMYzUXm
mmmmmzzzmxmzxzXXmzmXYUCOMMMWWWWWWWWWWWWWWWWWWWWWWOUYXzxmXYYY
OOCCCMMOzmYUYXz+XmmmXmXUUYYYXXXm;WWUYYYYYYYmxmXmzmXXXXmx,xzz
MMMMMMMxYUUUUUUUmOUUOMMMMMMMMMMO+WWxMMMMMMMMMUXXXmmzzXUCOMmm
UCMMMMM+UUYmx;,.,zUYzCMMMMMMMMMUXWMzMMMMMMMMMMMMMMMMMMMMMUmC
+CMMMMUz+,    ,zUUUUmYMMMMMMMMMzCWzOMMMMMMMMMMMMMMMMMMMMUmCC
+OMUmzYUUUYXmXUUUUYmmXXCWMWWWMWxMmOMMMMMMMMMMYOMMMMMMMCmYCCU
+OYUmxUYXmmmmmmmmzXUUUUmzCWWWWUmXMMMMMMMMMMMCmmMMMMMCXXCCCCC
xUmCXCUY+UUUUUUUUUUUUUYYUmUUOWXCMWMMMMMMMMMCmCzOMMUXYCOYXCCY
U+UYUUU;mUUUXXXXXXmYUXCWUx;++UMMWMMMMMMMMMUYCOmCUXUCCCYYUCUC
OCUUX;. +UUXMWWUXYOYzOWX.....;MMMMWMMUUUUCOCUOYxYCOOUYUXUCCC
CUCz... ,UmMWM;....XYUW;.....+MOUUX;......zCXCUUOOUYCCCUCCCC
UU;.... .YzWWU......mXWU,..,,zXXX;  .......CCXOOUYCOCCCCCCCC
U,.....  mXYWW+....,+XmYYXmzYUUU,   ...  .YOOUUUUOOCCOOOCCCU
+......  zUXmUCXx;;zYUUUUUUUUUUU,      .x+OOOOUXOOOOOOOCCCCU
.......  XUUUUXXXXYYYYUUUYUUUUUUYz+;+xmUzUOOOOCOOOOOOOCCCCUC
+.....  ;UUYUUUYYXzmmxmXYYYUUUUUUUUUYXmmUOCOOOOOOOOOOOOCCCCC
U,.... ;YYYXYYYYXmxzXY,Xm+zxzzzmmxmmmUOOOCOUOOOOOOOOOCOOCCCC
xzXzz;XYYYYYYYYYYYYzxz,...,mXMMWUYOUUOOOOOCUCOOOUYXXXXXXYYYY
+XXXm+YYXYXYYYXYYYYYYmz+;xXXXXOUXCUYOOOOCOUOUOOOmXmx+++++xm+
xUUY;XXmzmxmXXXXXXXYYYYXmzzmCUzxCCXCOOOOOUCUCOOOXUMOCUUCOOO+
mUUmxYYzmX+zXXXXXXXXYYYYYYYxYUUYCUYCCCCOCCUUCCOOXXOOMOOOOOO+
mUUzmUUxmYzzXXXXXXXXXXXXXXXX+UUCCYCCCCCCCCUCCOOOUzOOOOOOOOO;
mUUxzUUzzYX+YYXXXXXXmXXXXXXX,CCUXUCCCCCCCCUCUXXXXxXXYYYYYYmm
mUUm+UUY;YU+YYYYYYYYYYYYXXYmxCCCCCCCCUCCCz+xmYYYUUUUUUYYYYYY

Later I did things like convert ASCII art back into images in various ways. asciiversion2color is not a great program (and didn't even work for me today, because pamarith has been is named pnmarith on this system, a hazard of the netpbm tools being old and reaching different places from different paths, but when I did git it working:

$ pnmscale -w 120 fine300.ppm > fine.ppm
$ asciiversion2color fine.ppm textfine.ppm
pnmarith: promoting second file to PPM
pnmarith: promoting second file to PPM
pnmarith: promoting second file to PPM
HTML version in textfine.ppm.html
$  cjpeg textfine.ppm > textfine.jpg
This is fine, in three color ASCII art

asciiversion2color creates three pbmascii images, one for each RGB channel (in the "ppm" pixmap), converts the ASCII to text-in-image form, then combines the three with a slight offset.

And texttopnm lets you specify an arbitrary tile for each character, as a way of introducing fonts. It has some code for UTF-8 fonts, but I've mostly used it with historic fonts.

$ (echo scale=5; echo 9/14 ) | bc
.64285
$ pnmscale -w 60 fine300.pgm | pnmscale -yscale .64285 > fine60.pgm
$ pgmtoascii fine60.pgm > fine60.txt
$ texttopnm -s .ppm -d font/9x14 fine60.txt 9x14.pgm
$ pnminvert 9x14.pgm |pnmtopng > 9x14.png
This is fine, IBM circa 1981 style

The 9x14 font is the IBM Monochrome Display Adapter hardware font from circa 1981. Quoting Wikipedia:

Each character is rendered in a box of 9×14 pixels, of which 7×11 depicts the character itself and the other pixels provide space between character columns and lines. [...] Each character cell can be set to one of 256 bitmap characters stored in ROM on the card, and this character set cannot be altered from the built-in hardware code page 437. The only way to simulate "graphics" is through ASCII art.

Most recently, I found someone else's code that was created for emulating monochrome bitmap graphics using Braille characters in Unicode in terminal windows. I loved it immediately, and forked it to add a pbmtodots still image converter.

⢅⢌⢔⠔⡔⡪⢊⢆⢣⢒⢕⢱⢑⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠑⡪
⠕⡕⢕⠕⡱⡡⡃⡇⢕⠜⡰⡱⢀⣿⣿⣿⣿⡿⠿⠿⣿⣿⣿⣿⣿⣿⢿⢿⣿⣿⣿⣿⣿⢿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠈⡪
⡣⡣⡣⢃⡣⡊⡢⢃⢕⢡⠪⡰⢨⣿⣿⣿⣿⣿⡗⢼⣞⠸⠃⣟⢹⣯⡐⠶⣿⣿⣿⣿⣿⢸⣅⡒⠿⣿⣿⣿⣗⠰⢶⣾⢹⡇⢻⠩⠏⠔⣂⣿⣿⣿⣿⣿⣿⣿ ⡪
⢌⢌⠪⠪⡰⢑⢅⠇⡆⡣⠱⠱ ⣿⣿⣿⣿⣿⣯⣽⣷⣸⣥⣿⢸⣿⣍⣥⣿⣿⣿⣿⣷⣸⣯⣩⣵⣿⣿⣿⣗⣼⣿⣟⣸⣇⣷⣼⣌⡛⣛⣿⣹⣿⣿⣿⣿⡗⠠⡣
⢕⢜⢜⢔⠔⡰⡠⡀⡄⡔⢔⢔⢕⡈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠠⡅⣁
⡪⡪⡪⡎⡏⡮⡺⡱⡝⣜⡜⡔⡅⡣⡢⡉⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⡰⡽⢜⠔
⠕⠏⠞⠎⠇⠗⠙⣁⣍⡈⠚⠓⠑⠣⡣⣣⣒⢆⢍⣉⠛⠟⠿⠿⢿⢿⢿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⡿⠟⠛⢋⢡⢄⠞⠝⡵⣱⢫
⡿⣾⣳⣷⣯⣿⡿⣯⠃⣔⢏⣏⡯⣒⢆⡡⢬⣡⣡⣡⣩⣤⢦⣾⣴⡶⣴⣴⣤⣤⣤⣤⠠⣿⣿⡟⣩⣩⣩⣉⣍⣩⣉⣉⠡⠥⣥⢤⡢⡆⡧⡫⡣⡳⡱⠑⠂⡤⣄⣅
⣿⢿⣽⡷⣿⢾⣟⠇⢼⢕⡯⣪⣺⢕⡯⣲⡩⢷⠟⠞⢿⡾⣿⢷⣯⣿⣻⣾⣻⣯⣿⡽⢐⣿⣿⡂⣽⡿⣽⣯⡿⣯⣿⣻⢿⣶⢦⣥⣡⣉⣌⣨⢤⣤⣴⡿⣽⣻⠇⡙
⡟⢻⣯⣿⣻⣿⣿⡈⢧⡳⡫⠚⠘⠈⢀ ⢀⢴⢝⡵⣊⢽⣟⣿⡷⣿⣻⣯⣿⡾⣷⡏⢸⣿⡿⢐⣿⣟⡿⣾⣟⡿⣾⣻⢿⣽⢿⣽⡿⣯⡿⣯⣟⣿⣯⡿⣯⡟⣨⢷
⡂⣽⡷⣿⣻⣾⠻⡃⡁⡁  ⠐ ⢀⡴⡹⣕⢯⢎⡇⣸⣿⣽⣿⢿⣯⡿⣾⣟⣿⡃⣽⣿⢃⣿⣻⣽⣿⢿⣽⡿⣯⣿⢿⣽⡿⣷⢿⣯⡿⣯⣿⢾⡷⣟⡟⣠⣟⣽
⡐⣽⣿⢏⠴⢡⢺⣪⢗⡽⣱⣣⢖⡞⣕⣏⢯⢎⡗⠝⣠⢕⡙⠷⣿⣟⣿⡿⣿⣽⣿⠠⡿⢃⣾⣿⣽⡷⣿⣻⣷⣿⢿⣽⣿⡋⢿⣻⡿⣯⣿⢿⣽⣻⡻⢃⢼⣺⣺⣺
⠠⡷⡏⣼⢨⡘⢵⢕⣕⢩⢚⠸⠑⠫⢚⢘⣡⢥⢴⢝⡵⣝⢼⢢⡑⢻⣿⣻⣟⣿⡝⡜⢥⣿⡷⣿⣽⢿⣯⡿⣾⣻⡿⣷⢟⢰⡩⣟⣿⢿⣽⣿⠯⢓⡼⣽⢽⣺⡺⣞
⡆⢟⢨⣗⡇⣟⣶⣥⢁⢞⡵⣫⢏⢯⡺⡕⡧⣳⣫⢞⡞⡮⡫⣃⣏⣤⣋⡛⢿⢿⣤⣾⡿⣷⣿⣯⡿⣿⣽⢿⣯⣿⢿⡽⡢⣟⡆⣟⣿⣟⢗⢣⢮⣳⣯⢓⢱⣻⣽⢱
⢷⡈⣼⢪⢗⡵⡛⠂⠸⡕⡯⣺⠹⣘⣨⣌⣍⣌⡚⠵⣫⢚⣼⣿⠟⠉⠈⠉⢂⠻⣿⡾⣿⡿⣾⣻⣿⣯⣿⣻⣿⣽⢛⡴⡯⣗⡇⢽⡞⢥⢾⢽⢽⣺⡪⣎⢮⡗⡧⣟
⢯⣟⢵⣫⡞⠈  ⠩⣫⢮⢣⣾⣿⣿⠛⠛⠛⢿⣦⠑⣽⣿⠇⠂⠈ ⠂ ⠈⣿⢿⣯⣿⣻⡿⣾⠻⠫⠟⠽⢷⣻⣟⠽⡽⣕⠁⣮⢿⢽⢯⠟⣴⢽⢆⢯⡺⣯⢷
⢟⣺⣽⠊ ⢀⠈ ⠈⢮⡃⣿⣿⡿⠁⢀ ⠂ ⠹⣇⢽⣿⡁⠠ ⠂⠈⡀⠨⣿⡻⠿⠝⠟⠁⠠ ⠄ ⢀ ⠱⣳⡏⣽⢵⣫⢷⣻⡛⣥⡯⣗⣿⢵⣫⡯⣗⣟
⡧⣗⠁ ⠄ ⠄ ⠈⠧⡃⣿⣿⣗ ⡀⠠⠈⢀ ⠗⠸⣿⣧⡀⠄⠐⢀⢄⢨⡢⣖⣕⠃  ⠠ ⡀⠐ ⡀ ⡷⡷⡱⣯⢿⡝⣢⣯⡷⡯⣗⣿⢽⣺⢯⢗⣟
⣟  ⠂ ⠂ ⠄ ⢭⢇⡹⣿⣷⣄ ⠠ ⢀⠠⠡⡧⣩⣉⡛⡓⢛⡡⣜⢮⡫⣞   ⠂ ⢀ ⠄  ⡮⣟⣯⣧⠫⡟⢬⣷⣯⢯⡯⣗⣿⢽⣺⣻⢽⡺
⠅ ⠐ ⠁ ⠂  ⢸⢕⣖⢍⠻⠷⡮⡤⠤⠐⣨⣺⢕⢗⣕⢗⡽⣕⢽⡪⡗⣝⡮⡂⠠ ⠠   ⢀⢤⢨⢯⣗⡷⣯⢷⣡⢿⣺⣞⣟⣾⣳⣻⢽⡺⣺⣻⢼
 ⠈ ⡈ ⠐ ⠂ ⢕⢗⡵⡫⣳⢕⡖⣖⢵⢻⢜⢜⢝⢵⢕⢯⢪⣺⡱⣫⢏⡮⣺⢕⡦⣄⢄⢄⣔⢼⣪⠃⣾⢽⣺⣻⡯⣟⣽⣽⣺⢾⣻⡾⣽⣺⡯⡯⣗⡧⣟
⡌ ⠄ ⠈ ⠄ ⠨⣫⢧⢫⡫⣺⢕⢽⢜⡕⢙⢬⣤⡑⠫⠺⡕⣗⢵⡹⣜⢵⢝⡼⣱⡣⡗⡯⡧⠓⡋⡤⣞⣿⢽⣺⣯⢯⣗⣷⢿⡽⣯⢷⣻⣷⢽⣺⡯⣗⣯⢷
⠷⠂ ⠈ ⠂ ⢤⢝⢎⢞⢕⡝⣜⢕⡯⣪⢔⠡⡒⡍⣋⠂⢶⢦⠌⣈⠊⢃⢋⢃⢋⣘⡈⡥⡤⡆⣷⣻⣽⣳⡯⣯⢺⣺⣻⢾⡽⣽⡽⣽⡽⣞⣞⣟⢾⣫⣗⣿⢽
⡒⢬⢺⠴⡤⡂⢕⡧⡫⡮⣣⡳⣹⢜⢵⢕⢧⢫⡣⡆⡡⠣⠂  ⡀⠁⢜⢪⡿⣿⢿⡏⢼⡽⡏⣾⣳⣟⣞⢷⣻⣝⢾⣫⣯⣯⡯⣗⢛⠳⡙⢓⢋⢋⣋⣚⣊⣚⢹
⠐⡵⡱⣹⠜⢐⢇⢏⠞⡎⡞⡜⣎⢞⢕⢕⢏⢎⡞⡵⢕⡕⡦⡡⡠⡠⣪⠏⢦⡙⢿⡏⢼⢽⡺⣱⣽⣺⡷⡯⣟⣾⢣⣟⢞⣾⡺⣯⢇⢢⣍⡈⡉⠉⠉⢈⠁⢉⣨⠐
⠸⣕⢽⡸⢀⢇⢇⡇⠕⡕⡅⢪⢪⢪⢪⢣⢫⢪⡪⡺⡱⣹⢜⢍⣊⢥⢔⠸⡯⣗⢍⠢⡯⣟⢕⣽⣺⣳⢯⣟⡷⣏⣾⡳⢽⣺⣽⢯⡇⢝⣷⡿⡾⡾⣞⣾⡾⣿⢽⡂
⠸⡵⣻⡊⠤⡫⡮⡊⢪⢪⡂⢕⢕⢕⢕⢕⢵⢱⢕⢝⡜⡎⡮⡣⣣⢇⡗⡅⢫⡳⣳⣪⡿⡽⣸⢮⣗⢿⡽⡾⣝⣞⣞⢼⣻⡺⣞⣿⢎⠸⣷⣻⣟⣿⣻⣾⡽⣯⢿⡀
⢸⡽⣺⡂⢹⡹⣪⡃⢹⢜⡆⠜⣜⢜⢜⢕⢕⢕⢕⢇⢇⢏⢎⢞⢜⡜⣜⢕⠨⣞⡕⣟⣞⡣⣟⣽⣺⡯⡯⡯⣗⡯⡮⣟⣼⢯⢷⢯⣗⠸⣻⣞⣷⣻⣞⣷⣻⣯⢿⠠
⠸⣺⢕⡇⠸⣪⢧⡃⠥⣳⢣⠘⡮⡺⣸⢜⢜⢜⢜⢜⢜⢜⢜⡜⡜⡜⡜⣜ ⡷⣝⣎⢖⡽⣕⣗⣷⢽⣫⡯⣗⣯⡫⣞⠷⠛⢋⣋⢬⣈⢙⠚⠞⠳⠝⠞⠗⠯⠓⢸
⡸⣕⢗⢇⠘⡵⡕⣗⠈⡞⣕⡂⢏⢞⢮⢺⡱⡫⣺⢱⡣⣳⢕⢵⢱⢕⢵⡑⢨⣽⢮⢗⣟⡮⣗⣗⣗⢟⡾⣽⢵⠓⠡⠤⡪⡝⡵⡱⣫⢺⡹⡝⣝⢞⡵⡳⡕⣗⢽⢱

It's no longer ASCII art, by a long strech, but it's very clearly identifiable in a modern text window.

My Alarm Clock


When I was a kid, maybe twelve, I got a German made wind-up alarm clock, the type with the two bells on top and separate springs for the clock and the alarm. That was my "daily driver" of an alarm for a long time. After I married, my wife hated it because the ticking was "loud" when she was trying to get to sleep. When one of the springs finally broke, probably about 2005ish, I switched to using my cellphone as an alarm. Some Nokia brick. Eventually I switched to a Audiovox flip "feature" phone.

The Audiovox was Nokia quality indestructable. (But it was in many ways a terrible phone.) I used it as a phone for years and then when I finally upgraded, I continued to use the Audiovox as an alarm clock for some more years. I replaced it only in 2016.

On the Audiovox the alarm mode had three alarms that could be set, and then enabled or disabled. I found that three different wake-up times covered 90+% of my usage: wake up on a day when the kids have school, wake up on a day when I have work but the kids don't have school, and weekend. Then there would be 5 to 10% of the time when I'd need a special time for something.

So when it came to replacing it, I wanted an alarm clock that would give me three different standard times and one special, and I wanted an interface that could let me set a week of them at once. I turned to a "pocketCHIP" (hereafter Pocketchip) tiny portable computer.

Screenshot of the program

Pink buttons show alarms on, white ones show alarms off. The day of week button turns off all alarms for that day. Time of day is shown on the current day of week. Code available from my github page, it's Perl with Tk for the X11 UI.

The Chip was a $9 computer, the "Pocket" part was an attachment to add a keyboard, touchscreen, battery pack, headers for GPIO etc, and a small hacking space. Overall it cost about $60, which was (and is) a steal compared to what a Raspberry Pi outfited as a laptop / tablet / netbook / other portable computer costs. As of 2020, you can still find new-old-stock on ebay for $50ish. Unlike a Pi, Chip was low-res: no HDMI option; and has built-in wifi and bluetooth and built-in storage. Now Pocketchip isn't great, it's got a take-you-back-to-the-late-1980s pixel count (480x272), a painfully awkward keyboard, no built-in speakers, and poor audio volume from the audio jack.

I modified mine adding a basic mono amplifier (PAM8230 from Adafruit) and a speaker attached to that. I also connected one of the GPIO pins to a standard 1/8th inch (2.5mm) audio jack, to use with an external button. There was ample room in the case for the amp, speaker, and extra jack. I did need to cut the plastic a bit to get get the proper parts sticking out.

Back of the modified Pocketchip

The green board in the upper left is for the GPIO 2.5mm jack, the blue board near it is the add-on amp, there's an on-off switch for the amp in the far upper left, and the speaker is squeezed in on the bottom right. The larger black board is the Chip $9 computer, and the silver thing below it is the rechargable battery.

Then I built a button out of a wooden box and Cherry D42 switch with a lever that I pulled out of some device. The lever holds the top of the box slightly open, pushing the lid down closes the switch. Big easy to push button for sleepy alarm silencing.

When the alarm fires, a program plays a sound file (repeatedly up to 30 times or 60 seconds as configured) and listens to a GPIO pin. Every 1/100th second the GPIO pin is sampled. With ten high samples and ten low samples — to allow for a switch normally open or normally closed — it decides that's enough and kills the sound playback and quits.

A look inside the button

This part was made entirely with reused stuff I had laying around.

I wrote the alarm software sometime in 2016 and have made only slight modifications to it since then. It works, and it works well. When I built myself a new bedside table last year, I designed it to have a place for the Pocket chip to hang, and suitable space for all the wires.

View of the installed alarm

I made the lamp first, then the alarm button to fit under the lamp, then the table that this all lives on. The drawer holds pencils and bookmarks, the current night time reading is on the table, the next books to read on the first shelf, books done reading go on the shelf below that. All of this is from scavenged or left-over material.

As this setup gets long in the tooth, I'm starting to think about what I do next. In particular, I worry about the Chip failing and/or the battery becoming useless. I've been thinking it might make a good learn Arduino project. I'd prefer to spend less than $100 on all parts, have a system with at least the current capabilties, including normally plugged in but with at least an hour's backup power to ride out blackouts. And a screen that I can easily turn off for darkness. It's been fun browsing, but I don't have a parts list yet.

Cut-and-paste as a scripting language


I'd say one, perhaps controversial, technique that has been very successful for me in computer system administration are scripts that are not intended to be run with a shell or other single tool but as a series of things to cut-and-paste. I frequently use such things in situations like:

  1. Drafting a proper scripts to be run from Puppet, Chef, Ansible, cron, etc.
  2. Massaging existing systems from one state into another before Puppet, Chef, or Ansible takes over management. This includes bootstrapping.
  3. Changing state in things not under Puppet, Chef, Ansible, etc, control, because, eg it's a database not an OS.
  4. The code is to be used as a runbook for what should be done in the case of an error.

In many cases the cut-and-paste script is something that should become a proper script if it is going to be done often enough, but see point one. A number of systems that need to be brought into a consistent state but for whatever reason are starting from a diverse set of existing states might need a script with a lot of branching. The cut-and-paste script has a human running it and human that can perform error recovery and new branch configurations with relative ease.

To point two: in some cases there will be enough systems to perform this on that some script is required, but the test to know which state should apply is a complicated one to script and it's much simpler to let a human decide the steps needed after which the test becomes simple and automation can take over.

And for point three: always you will have some aspect of the system that is supposed to be above the level of the automation but for which a degree of control is sometimes needed.

Lastly point four: a runbook that provides as exact as possible set of steps to follow allows for more error-free recovery from issues when they do arise. Sometimes this set of steps can be shown to be reliable enough to create scripts (point one again) that perform autorecovery.

I think at this point it becomes clear that the cut-and-paste script is a useful developmental tool for creating robust automation. At least, I frequently find it to be so.

Some examples of situations I've used cut-and-paste scripts:

  • I first developed this method managing a fleet of web servers in the early '00s. The site and the config were all in source code control, but triggering syncs and Apache config reload were manual actions. A loop like for host in $(< hostlistfile); do ssh $host "cd /webroot && p4 sync"; done would work, but it wouldn't be nearly as close to simultaneous as opening sixteen xterms each sshed in to a web server (easy to locally script) and then pasting the commands as fast as possible in each. Later (much later) that company started to use Chef, where the knife command could do a good job of replacing such stuff.
  • Using a web browser to connect to "console" of a newly installed system, using xdotool to type the commands to bootstrap networking onto the system. That "console" was some weird javascript app that didn't accept "paste", hence getting creative with xdotool to emulate typing in it. That network had no DHCP and needed a static IP on the correct VLAN before it could connect. I broke down the commands into several xdotool commands for two reasons, (a) to keep the command lines from getting unreasonably long (ie personal taste), (b) to not "typeahead" when starting programs like a file editor, and (c) to not have to encode which static IP to use, instead just getting right to that point, entering it by hand then continuing with the script. Finally the script ended with rebooting, and then I could take over with ansible, the config management tool of choice there.
  • Filling out a number of web forms where an API should be used, but there is resistance to making the API usable. Specifically, of late, that has been managing "silences" during system updates in Prometheus Alertmanager. Due to the login being behind Okta, command line tools can't authenticate. There is a proposed fix for this, but it hasn't been prioritized yet. In the meantime, I'll just open vi in an xterm and put field values to use on separate lines for quick triple-click copying. Typically I'll have two files open one with the things that are the same for every "new silence" and one for the hostnames changing between them.

One thing that has helped with all of this is the standard X11 paradigm of "select is copy" and "mouse middle button is paste". I can double click on a word in one window, move the mouse to another window and paste it, with no keyboard. Or triple click for line, and then paste it in as many windows as I want with just a single click each. It becomes the opposite of hands never leave keyboard, where the script run is completely done with hand never leaves mouse (at least until the script needs an edit). This cut and paste paradigm never caught on, and it makes me less productive on Macs and Windows. Macs are in some ways the worst, because it's Unix but it's not X11: almost, but not quite. (And to add to the pain, Mac keyboard shortcuts are nothing like xterm ones.)

Of course, if you do need to type some part of the cut-and-paste script learning and using keyboard shortcuts for that, particularly paste, are the way to go. Consider this simple script:

for host in $(< hostlist ) ; do
        # need to set EDITOR enviornment variable after sudo
	ssh -t $host "sudo env EDITOR=ex crontab -e"
done
# paste to ex:
#	:g /day$/ s, \* /, 1-5 /, | x 

One can copy that global command, which finds lines ending in "day" and edits the * before the / to be 1-5. For a crontab like:

MAILTO=cron-list@example.com
3 9 * * * /usr/local/bin/start-day
10 2 * * * /usr/local/bin/run-backup

This will change run every day at 9:03 am start-day program to only run on weekdays: 3 9 * * 1-5 /usr/local/bin/start-day and save the file. The for loop will log in to each host in the list of hosts, possibly prompting for an ssh password (depending on how you have that set up) and probably prompting for a sudo password (depending on how you have that set up). It would be quite reasonable to run the loop, enter passwords by hand, then hit <shift-insert> to use the default xterm paste primary keybinding to paste the script whereupon cron is updated, you are logged out, and move on to next host, so you end up just typing password<enter><shift-insert> password<enter><shift-insert> password<enter><shift-insert> ....

Some tricks of the trade that I've picked up over the years of doing this:

  • Many things that don't immediately look cut-and-pastable can be made so by rethinking them. File edits with ex or ed are easier to script than vi; xdotool can be used to control things in windows.
  • Whole lines are easier to copy than fragments, triple-click will select a line.
  • Learn exactly how to include or not include trailing new lines in your copies, it can make a lot of difference.
  • Use variables like you would in a regular script loop for things that will vary from run to run, even if each run is on a separate host. This lets you separate variable setting pastes from command run pastes.
  • Setting variables with shell commands instead of manually makes for a smoother experience. grep, head, and tail, are good for selecting lines from output. cut, sed, and grep -o are good for selecting part of a line from output:
       host=$(hostname -f)
       homeuse=$(df /home | tail -1 | grep -o '[0-9]*%' | cut -f 1 -d %) 
  • Some shell line break methods are more fragile than others. Consider:
    # Pasted as a group, both of these run, even if you only want the
    # second to run if the first succeeds.
    /bin/false
    /bin/true
    
    # Pasted as a group, the second will run only if the first succeeds
    /bin/true &&
    /bin/false
    
    # The backslash will escape the following whitespace character.
    # If you have a trailing space, that's the whitespace that will
    # be escaped, not the newline.
    [ -f "$somefile" ] && 
    	head "$somefile" \ 
    	| next command
    
    # Written like this, trailing whitespace is ignored, the pipe
    # alone is enough to keep the shell looking for more to do.
    [ -f "$somefile" ] && 
    	head "$somefile" | 
    	next command
    

And remember, cut-and-paste scripts are a good way to write a real script, slowly dealing with all the error cases to handle and best ways to make the code universal. Use them to compose your startup scripts, your cron jobs, your configuration management tooling, your runbooks, and coding your self-healing systems.

slowcat.c and signature.c


A discussion in the Usenet group comp.sys.raspberry-pi about browsers turned to ASCII art when I brought up concerns about showing ASCII art in web pages (based on the recent notint screenshots here). And that discussion had one Charlie Gibbs reminisce about the "Andalusian Video Snail".

I found the file for him at https://grox.net/misc/vt/ — it's an ASCII art animation with VT100 escape codes so viewing it is tricky. Those animations are typically highly dependent on viewing on slow terminals, and modern hardware is anything but a slow terminal.

At grox.net there is also a slowcat.c which easily compiles. That code is essentially a loop of getchar();nanosleep();putchar(); to delay output. It worked okay for a few videos I tried, but the snails did not look so good. And it would not slow down STDIN, which would be handy for a program I wrote (more later).

So the biggest problem with the Grox slowcat.c is while it adds delays, it doesn't disable output buffering, so output ends up being large chunks showed without delay and then a delay for the next large chunk. That doesn't suit that particular animation well. Other problems include a "cat" that only shows exactly one file instead of arbitrary numbers, an awkward interface for delays (how many nanoseconds do I really want? and why does the code use usecs for presumably μseconds), and arg parsing oddness.

I decided to rewrite slowcat. I disabled buffering, switched to usleep(), employ getopt() and an arbitrary number of files, and added a option to have the program calculate a delay to emulate a particular baud rate, which is how terminal speeds were measured when these were written. Quite likely a baud of 1200 or 9600 was assumed by the animator. Baud is a measure of bits per second, so 9600 works out to 150 characters per second, or about 13 seconds for an 80x24 terminal screen. When I test time slocat -b 9600 snails.tv takes 3m33.23s while time cat snails.tv takes 0.47s. On my system stty tells me 38400 baud, which would be 4 times faster, but since it actually runs 453(ish) times faster, the baud equivalent is closer to 4,500,000 than 38,400. For most purposes, that faster speed is much appreciated.

So back to signature.c, by which I mean this program:

Elijah
------                                         /* gcc signature.c -lm */
main(){int i,j,k,l;for(i=-12;i<13;i++,printf("\033[H")){for(l=61968*457+
1,l*=5;l>8;l>>=4)p(97-(l>>22)+(l&15));for(j=-12;j<12;){for(k=j+12?-12:-6
;12>k;)p((l=k*k+++i*i+j*j)<12*12?(l=sqrt(l))[".,:;iIJYVSOM"]:32);j++<11?
p(10):0;}}puts("Elijah ");}p(int m){printf("%c",m);}

I use it in place of a .signature on select Usenet posts. I wrote it approximately 1995. Over the years, it's been used perhaps one or two dozen times, and I have made at least five variants with different email addresses, each of which has been reformated to have the lines the same length.

(An aside on .signatures: I have never used a proper .signature, that is to say a block of standard text appeneded automatically to the end of my posts or email. I have always done it manually, and, for Usenet posts, 99.9% of them are unique entries composed for that particular post. I have about 30 programs, most in Perl, that I have used and reused from time to time. Since I don't consider them true signatures, and do consider them part of the post, I do not use proper signature cut lines — dash dash space on a line by itself — which annoys some people, sometimes.)

The origins of that program are interesting. The program came to me in a dream where I visualized an ASCII art movie of cross-sections of a sphere that changes "color" closer to the core. The colors are represented by the characters in the string ".,:;iIJYVSOM" and the code uses some basic IOCCC obfuscation tricks, like using C arrays backwards: (offset)["array to index"], but is mostly pretty readable once reformatted. The use of +++ is very classic me, since I enjoy testing how far I can push a parser to accept the same character over and over. My extreme is perhaps this Perl one:

sub S(){@s=caller($/);$s[3]=~s s\w+:+ss&&print$s[3].q. .}$/=$^=~s/\S+/\n/;
$_="Just (eli) Another (the) Perl (bearded) Hacker";sub s($){eval$_[0];$/}
while(s&&&&& &s(qq&sub$^&.$&.q&{\&S}&)&& &{$&}&&s&&&){$/}$\=$^;print"\b,";

Five ampersands in a row, then a space because the parser choked on ampersand six. s&&& runs a substitution using the previous regexp, so the RE field between s& and the middle & is empty and replaces the match with an empty string, the middle & and third &. If that succeeds (&&) call subroutine s: &s. I needed a space on either side of the && and operator as a hint to the parser about what's going on, and I choose to go with after to get five in a row. Then I squeeze as many extra ampersands as I can in the rest of that line. I'm pleased to say that since I wrote that, the Perl parser has improved to the point that the six ampersand in a row version now works: a fragment like (s&&&&&&s( is either a nightmare or a wonderous sight depending on your tastes.

But I digress. The dream version of sphere.c did not include the use of the number 28319377 to represent "Elijah" printed on every frame, I added that when I decided that sphere.c should become signature.c. It's obfuscated enough that the average schmoe won't be able to change it, but could perhaps remove it.

But like those other ASCII art movies, signature suffered from the speed-up of terminals. The visualization cannot be appreciated at such lightning rates. The fix to have slowcat work on STDIN was added with my signature program in mind: signature | slowcat -b 9600

My slowcat and the latest version of signature.c, now fixed to have a more circular sphere, are available at github, along with various animations (the animations from grox.net and textfiles.com).