雨コマンド

4-3.文字列置換


2013年公開の映画『言の葉の庭』です。
深海監督の作品は、桜の花びらの落ちるスピードを『秒速5センチメートル』ってタイトルにした作品から好きで繰り返し見ています。今年は『君の名は』も公開されるので、ぜひ映画館で見てください。
雨って元々詩的ですよね。夕立は出会いの予感、激情の時は土砂降りになるし、雨が上がると虹も出て希望も表現できる。『言の葉の庭』は、雨と心が上手く表現されていてうっとりします。感情移入し過ぎて主人公みたいに、一から靴作りDIYしたくなったほどです。秦基博が歌う『Rain』と彼の声もマッチしていて、こちらにもハマります。『Rain』は大江千里が80年代に作った曲なのですが、その歌詞や曲の雰囲気は20年以上の時を経て、この映画のために作曲されたんじゃないかと思わせるほどです。

rainコマンドは、端末ウィンドウに雨を降らせるコマンドです。bsdgamesパッケージに含まれています。

~$ sudo apt install bsdgames
~$ rain
rain
~$ man rain

名称
     rain — 雨の降る様を表示する

書式
     rain [-d delay]

解説
     rain による表示は、 VAX/VMS での同名のプログラムに倣って作成されたもので
     す。 適切な効果を得るためには、端末を 9600 ボーに設定しておくか、 -d オプ
     ションを使用して更新間隔をミリ秒単位で指定する必要があります。 適切な間隔
     は 120 ですが、デフォルト値は 0 です。

作者
     Eric P. Scott


manの説明にあるように、-dオプションで表示をゆっくりにして初めて雨のアニメーションが分かります。空を見上げたときの、雨だれが落ちてくる様子にも見えますね。
よく見ると、この雨だれの数は、画面内に5個しか表示されてません。そしてすべて異なる表示となっています。どのようなプログラムになっているのでしょうか。

rain

ソースを入手して見ていきましょう。/usr/src以下にダウンロードします。

~$ su -
~# cd /usr/src
/usr/src# apt-get source bsdgames
/usr/src# ln -s bsdgames-2.17/ bsdgames
/usr/src# exit
~$

rainコマンドなので、たいていrain.cです。findで探します。

~$ find /usr/src/bsdgames -name rain.c
~$ 

あれ、おかしい。表示されません。rain.cではないんでしょうか。
実は、/usr/src/badgamesは、先ほど作ったシンボリックリンクですので、findのデフォルト実行ではシンボリックリンクは辿れないのです。オプション-Lが必要になります。
では、-Lをつけてもう一度。

~$ find -L /usr/src/bsdgames -name rain.c
/usr/src/bsdgames/.pc/rain-Update-default-delay.(右省略)
/usr/src/bsdgames/rain/rain.c
~$ 

ありました。rain/rain.cです。行数は、

~$ !! |xargs wc -l
find -L /usr/src/bsdgames -name rain.c |xargs wc -l
 158 /usr/src/bsdgames/.pc/rain-Update-default-(右省略)
 158 /usr/src/bsdgames/rain/rain.c
 316 合計
~$ 

158行ぐらいなら、全文表示してみましょう。

~$ cat -n /usr/src/bsdgames/rain/rain.c
     1	/*	$NetBSD: rain.c,v 1.17 2004/05/02 21:31:23 christos Exp $	*/
     2	
     3	/*
     4	 * Copyright (c) 1980, 1993
     5	 *	The Regents of the University of California.  All rights reserved.
     6	 *
     7	 * Redistribution and use in source and binary forms, with or without
     8	 * modification, are permitted provided that the following conditions
     9	 * are met:
    10	 * 1. Redistributions of source code must retain the above copyright
    11	 *    notice, this list of conditions and the following disclaimer.
    12	 * 2. Redistributions in binary form must reproduce the above copyright
    13	 *    notice, this list of conditions and the following disclaimer in the
    14	 *    documentation and/or other materials provided with the distribution.
    15	 * 3. Neither the name of the University nor the names of its contributors
    16	 *    may be used to endorse or promote products derived from this software
    17	 *    without specific prior written permission.
    18	 *
    19	 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
    20	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    21	 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    22	 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
    23	 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    24	 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    25	 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    26	 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    27	 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    28	 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    29	 * SUCH DAMAGE.
    30	 */
    31	
    32	#include <sys/cdefs.h>
    33	#ifndef lint
    34	__COPYRIGHT("@(#) Copyright (c) 1980, 1993\n\
    35		The Regents of the University of California.  All rights reserved.\n");
    36	#endif /* not lint */
    37	
    38	#ifndef lint
    39	#if 0
    40	static char sccsid[] = "@(#)rain.c	8.1 (Berkeley) 5/31/93";
    41	#else
    42	__RCSID("$NetBSD: rain.c,v 1.17 2004/05/02 21:31:23 christos Exp $");
    43	#endif
    44	#endif /* not lint */
    45	
    46	/*
    47	 * rain 11/3/1980 EPS/CITHEP
    48	 * cc rain.c -o rain -O -ltermlib
    49	 */
    50	
    51	#include <sys/types.h>
    52	#include <curses.h>
    53	#include <err.h>
    54	#include <signal.h>
    55	#include <stdio.h>
    56	#include <stdlib.h>
    57	#include <termios.h>
    58	#include <unistd.h>
    59	#include <errno.h>
    60	#include <limits.h>
    61	
    62	static volatile sig_atomic_t sig_caught = 0;
    63	
    64	int main(int, char **);
    65	static void onsig(int);
    66	
    67	
    68	int
    69	main(int argc, char **argv)
    70	{
    71		int x, y, j;
    72		long cols, lines;
    73		unsigned int delay = 120;
    74		unsigned long val = 0;
    75		int ch;
    76		char *ep;
    77		int xpos[5], ypos[5];
    78	
    79		/* Revoke setgid privileges */
    80		setregid(getgid(), getgid());
    81	
    82		while ((ch = getopt(argc, argv, "d:")) != -1)
    83			switch (ch) {
    84			case 'd':
    85				val = strtoul(optarg, &ep, 0);
    86				if (ep == optarg || *ep)
    87					errx(1, "Invalid delay `%s'", optarg);
    88				if (errno == ERANGE && val == ULONG_MAX)
    89					err(1, "Invalid delay `%s'", optarg);
    90				if (val >= 1000)
    91					errx(1, "Invalid delay `%s' (1-999)", optarg);
    92				delay = (unsigned int)val * 1000;  /* ms -> us */
    93				break;
    94			default:
    95				(void)fprintf(stderr, "Usage: %s [-d delay]\n",
    96				    getprogname());
    97				return 1;
    98			}
    99	
   100		initscr();
   101		cols = COLS - 4;
   102		lines = LINES - 4;
   103	
   104		(void)signal(SIGHUP, onsig);
   105		(void)signal(SIGINT, onsig);
   106		(void)signal(SIGTERM, onsig);
   107	
   108		curs_set(0);
   109		for (j = 4; j >= 0; --j) {
   110			xpos[j] = random() % cols + 2;
   111			ypos[j] = random() % lines + 2;
   112		}
   113		for (j = 0;;) {
   114			if (sig_caught) {
   115				endwin();
   116				exit(0);
   117			}
   118			x = random() % cols + 2;
   119			y = random() % lines + 2;
   120			mvaddch(y, x, '.');
   121			mvaddch(ypos[j], xpos[j], 'o');
   122			if (!j--)
   123				j = 4;
   124			mvaddch(ypos[j], xpos[j], 'O');
   125			if (!j--)
   126				j = 4;
   127			mvaddch(ypos[j] - 1, xpos[j], '-');
   128			mvaddstr(ypos[j], xpos[j] - 1, "|.|");
   129			mvaddch(ypos[j] + 1, xpos[j], '-');
   130			if (!j--)
   131				j = 4;
   132			mvaddch(ypos[j] - 2, xpos[j], '-');
   133			mvaddstr(ypos[j] - 1, xpos[j] - 1, "/ \\");
   134			mvaddstr(ypos[j], xpos[j] - 2, "| O |");
   135			mvaddstr(ypos[j] + 1, xpos[j] - 1, "\\ /");
   136			mvaddch(ypos[j] + 2, xpos[j], '-');
   137			if (!j--)
   138				j = 4;
   139			mvaddch(ypos[j] - 2, xpos[j], ' ');
   140			mvaddstr(ypos[j] - 1, xpos[j] - 1, "   ");
   141			mvaddstr(ypos[j], xpos[j] - 2, "     ");
   142			mvaddstr(ypos[j] + 1, xpos[j] - 1, "   ");
   143			mvaddch(ypos[j] + 2, xpos[j], ' ');
   144			xpos[j] = x;
   145			ypos[j] = y;
   146			refresh();
   147			if (delay)
   148				usleep(delay);
   149			else
   150				tcdrain(STDOUT_FILENO);
   151		}
   152	}
   153	
   154	static void
   155	onsig(int dummy __attribute__((__unused__)))
   156	{
   157		sig_caught = 1;
   158	}

着目する箇所としては、まずは77行目のxpos、yposの配列の数が、5個であるということで、雨だれの画面上の数を表しています。

次に、121行目から雨だれのキャラクタの表示となっていますが、xposやyposでgrepすると分かりやすいです。

77:	int xpos[5], ypos[5];
110:		xpos[j] = random() % cols + 2;
121:		mvaddch(ypos[j], xpos[j], 'o');
124:		mvaddch(ypos[j], xpos[j], 'O');
127:		mvaddch(ypos[j] - 1, xpos[j], '-');
128:		mvaddstr(ypos[j], xpos[j] - 1, "|.|");
129:		mvaddch(ypos[j] + 1, xpos[j], '-');
132:		mvaddch(ypos[j] - 2, xpos[j], '-');
133:		mvaddstr(ypos[j] - 1, xpos[j] - 1, "/ \\");
134:		mvaddstr(ypos[j], xpos[j] - 2, "| O |");
135:		mvaddstr(ypos[j] + 1, xpos[j] - 1, "\\ /");
136:		mvaddch(ypos[j] + 2, xpos[j], '-');
139:		mvaddch(ypos[j] - 2, xpos[j], ' ');
140:		mvaddstr(ypos[j] - 1, xpos[j] - 1, "   ");
141:		mvaddstr(ypos[j], xpos[j] - 2, "     ");
142:		mvaddstr(ypos[j] + 1, xpos[j] - 1, "   ");
143:		mvaddch(ypos[j] + 2, xpos[j], ' ');
144:		xpos[j] = x;
~$ 
 o
↓
 O
↓
 -
|.|
 -
↓

 -
/  \
| O |
\  /
 -
↓



あれ、何か変です。雨だれのタイプが4個しかありません。画面上の5箇所に表示されていたので、もう一つあるはずです。

配列を使っていないこの行を見落としていました。

120 mvaddch(y, x, ‘.’);

さて、原理は分かったので、コマンドラインで挑戦してみましょう。
雨だれの絵は、テキストファイルに以下のように納めておきます。

~$ cat -n raindrop.txt
     1	     
     2	     
     3	  .  
     4	     
     5	     
     6	     
     7	     
     8	  o  
     9	     
    10	     
    11	     
    12	     
    13	  O  
    14	     
    15	     
    16	     
    17	  -  
    18	 |.| 
    19	  -  
    20	     
    21	  -  
    22	/   \
    23	| O |
    24	\   /
    25	  -  
~$ 

raindrop.txtを使ってそれぞれの雨だれを表示するのは、以下のようにsedを使います。

~$ for i in `seq 1 5 25`;do ((j=$i+4));sed -n "${i},${j}p" raindrop.txt;done

それぞれの雨だれにおいて、改行が入ると、コンソールの一番右にカーソルが移動してしまいます。perlにて改行を1行下、5桁左にカーソル移動するエスケープシーケンスに置換しています。

~$ while :;do clear;for i in `seq 1 5 25`;do perl -e 'printf "\x1b[%d;%dH",int rand 20, int rand 80';((j=$i+4));sed -n "${i},${j}p" raindrop.txt | perl -pe "s/\n/\x1b[1B\x1b[5D/" ;done;sleep 1;done

上記コマンドの実行動画ですが、砂利が混ざったような雨になりました。

catsdogs

コメント

タイトルとURLをコピーしました