Let It Snow In Your Terminal

Copy and paste the commands below into your bash shell to have it "snow" in your terminal. Make sure you have gawk installed. Standard awk will not work due to buffering problems.

clear;while :;do echo $LINES $COLUMNS $(($RANDOM%$COLUMNS));sleep 0.1;done|gawk '{a[$3]=0;for(x in a) {o=a[x];a[x]=a[x]+1;printf "\033[%s;%sH ",o,x;printf "\033[%s;%sH*\033[0;0H",a[x],x;}}'

Note: If you want to put this in a shell script, you'll need to either manually set COLUMNS and LINES to whatever your width and height of your terminal is in characters or 'export COLUMNS LINES' in your shell before running the script. These are not normally in the shell exported environment variables. You can also use stty size to get the values from a script, which is probably the better way anyways for a script.

Below is the same algorithm, but also passing it the unicode snowflake. Its recommended that you increase the font size above 18 to make the snowflake's details visible..

clear;while :;do echo $LINES $COLUMNS $(($RANDOM%$COLUMNS)) $(printf "\u2744\n");sleep 0.1;done|gawk '{a[$3]=0;for(x in a) {o=a[x];a[x]=a[x]+1;printf "\033[%s;%sH ",o,x;printf "\033[%s;%sH%s \033[0;0H",a[x],x,$4;}}'

There are actually a few other snowflakes in Unicode and other characters that are visually interesting. Experiment around with the value of \u2743.

Note for Mac users: You will probably need to install the gawk command via ports or something. Also, I couldn't get the unicode characters to print using the printf that comes installed with Snow Lion (ironically) and the default bash is too old to support it with the builtin printf. You may have to upgrade bash to do this.

Make it snow in under 140 characters (Added 2016-12-19)

You can't squeeze blood from a turnip, but you can squeeze snow from a perl.

In the back of my mind, I always knew there was some way to cram enough functionality into a command so that I could make it snow in the terminal and fit it into a single tweet. A couple of nights ago, I carved out a Perl command that perhaps justifies people's hatred of Perl and I learned a few tricks along the way.

yes $COLUMNS $LINES|pv -qL50|perl -ne'$|=1;($c,$r)=split;$s||=$"x($c*$r);print$s;$s=$"x$c.$s;substr$s,rand$c,1,"*";$s=substr$s,0,$c*$r+$c;'

So you don't have to decode that obtuse mess of code, I'll explain how this works. Instead of using arrays, I generate a string that contains the same number of character blocks as you have in your terminal (columns * rows). Then on each iteration of the loop, the program inserts a new line at the beginning of the string that is the same width as your terminal along with a '*' somewhere in the line choosen by a call to rand(). Inserting the line this way causes all the other lines with "snowflakes" in them to be pushed down towards the bottom. Then before the end of the loop, I truncate off the last $COLUMNS of characters and wala we have snow fall. Its not quite as fluid as the previous methods, but it works and it fits.

My first challenge to fit this in under 140 was how I was going to get the width and height variables ($COLUMNS and $LINES) into the Perl command. Using yes seemed the best option. Those variables aren't normally part of the shell's exported environment variables so I can't just call them from within perl. The next challenge was that without using a module, the only way I know of to sleep in perl with subsecond resolution is using the command select(undef,undef,undef,0.1); But this already is 30 characters long. Since I was already going to use yes to send lines of output into the while loop, it made since to use the pv command to control the rate at which the lines are sent. This is what 'pv -qL50' does. It limits the flow rate to 50 lines per second, which in my testing yielded the best results. The $|=1 is also important to turn off buffering to make the animation more fluid. Its not that great of an animation, but it works.

Next I needed to find a way to trim the size of the normal explicit while loop, I knew about the -n option to perl, which wraps the code inside a while loop. However, this doesn't make it easy to predefine variables outside of the loop, such as initializing the display string to be only spaces. Fortunately, perl provides an "assign default value" operator that I had never used before (Hey I learned something in all this!). This is the '$s||=$"x($c*$r);' part. Its more properly written as

$s ||= " "x($c*$r);

To save 1 character, I exploited the fact that $" in perl is set to a space by default. This is another perl golfing trick. The " "x$($c*$r) part creates a string of the previous character that is $c*$r characters long, or the total characters in your terminal. Another thing perl allows you to do is to omit the ( ) around the arguments to many functions as well as omit the space before a variable argument. This makes for some ugly code. But again, it fits.

During the making of the perl command I searched around for anyone who might have already made it fit into 140 and couldn't find anything, however I did find that this whole "make it snow in the terminal" trend may have been started by this page back in 2011. All the other "make it snow in the terminal" posts and challenges that I found were created just a few weeks after I first created this page in November 2011 or thereafter. So I guess I had the original idea.

Even shorter

And of course in good tradition, no sooner than you set a record that someone breaks it. @BubBobz sent me this command on December 21st:

for((I=0;J=--I;))do clear;for((D=LINES;S=++J**3%COLUMNS,--D;))do printf %*s.\\n $S;done;sleep 1;done

Looking at his timeline, I see that he had been inspired by my post and was working all night on making the commands shorter and shorter until he created the one above that is only 100 bytes, besting my previous one by 38 characters. Wow.

climagic home page

Created: 2011-11-28, updated 2016-12-19

blog comments powered by Disqus