何に使うかわからないrevを使って文字コードチェック

何に使うかわからないrevコマンドですが、もっと活用する術はないかと、revコマンドのソースを確認してみました。

~$ apt-get source util-linux
~$ cat -n util-linux-2.25.2/text-utils/rev.c
     1	/*-
     2	 * Copyright (c) 1987, 1992 The Regents of the University of California.
     3	 * All rights reserved.
     4	 *
     5	 * Redistribution and use in source and binary forms, with or without
     6	 * modification, are permitted provided that the following conditions
     7	 * are met:
     8	 * 1. Redistributions of source code must retain the above copyright
     9	 *    notice, this list of conditions and the following disclaimer.
    10	 * 2. Redistributions in binary form must reproduce the above copyright
    11	 *    notice, this list of conditions and the following disclaimer in the
    12	 *    documentation and/or other materials provided with the distribution.
    13	 * 3. All advertising materials mentioning features or use of this software
    14	 *    must display the following acknowledgement:
    15	 *	This product includes software developed by the University of
    16	 *	California, Berkeley and its contributors.
    17	 * 4. Neither the name of the University nor the names of its contributors
    18	 *    may be used to endorse or promote products derived from this software
    19	 *    without specific prior written permission.
    20	 *
    21	 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
    22	 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    23	 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    24	 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
    25	 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    26	 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    27	 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    28	 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    29	 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    30	 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    31	 * SUCH DAMAGE.
    32	 *
    33	 * Modified for Linux by Charles Hannum (mycroft@gnu.ai.mit.edu)
    34	 *                   and Brian Koehmstedt (bpk@gnu.ai.mit.edu)
    35	 *
    36	 * Wed Sep 14 22:26:00 1994: Patch from bjdouma <bjdouma@xs4all.nl> to handle
    37	 *                           last line that has no newline correctly.
    38	 * 3-Jun-1998: Patched by Nicolai Langfeldt to work better on Linux:
    39	 *	Handle any-length-lines.  Code copied from util-linux' setpwnam.c
    40	 * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
    41	 *	added Native Language Support
    42	 * 1999-09-19 Bruno Haible <haible@clisp.cons.org>
    43	 *	modified to work correctly in multi-byte locales
    44	 * July 2010 - Davidlohr Bueso <dave@gnu.org>
    45	 *      Fixed memory leaks (including Linux signal handling)
    46	 *      Added some memory allocation error handling
    47	 *      Lowered the default buffer size to 256, instead of 512 bytes
    48	 *      Changed tab indentation to 8 chars for better reading the code
    49	 */
    50	
    51	#include <stdarg.h>
    52	#include <sys/types.h>
    53	#include <errno.h>
    54	#include <stdio.h>
    55	#include <stdlib.h>
    56	#include <string.h>
    57	#include <unistd.h>
    58	#include <signal.h>
    59	#include <getopt.h>
    60	
    61	#include "nls.h"
    62	#include "xalloc.h"
    63	#include "widechar.h"
    64	#include "c.h"
    65	#include "closestream.h"
    66	
    67	wchar_t *buf;
    68	
    69	static void sig_handler(int signo __attribute__ ((__unused__)))
    70	{
    71		free(buf);
    72		_exit(EXIT_SUCCESS);
    73	}
    74	
    75	static void __attribute__ ((__noreturn__)) usage(FILE * out)
    76	{
    77		fprintf(out, _("Usage: %s [options] [file ...]\n"),
    78			program_invocation_short_name);
    79	
    80		fprintf(out, _("\nOptions:\n"
    81			       " -V, --version   output version information and exit\n"
    82			       " -h, --help      display this help and exit\n"));
    83	
    84		fprintf(out, _("\nFor more information see rev(1).\n"));
    85	
    86		exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
    87	}
    88	
    89	static void reverse_str(wchar_t *str, size_t n)
    90	{
    91		size_t i;
    92	
    93		for (i = 0; i < n / 2; ++i) {
    94			wchar_t tmp = str[i];
    95			str[i] = str[n - 1 - i];
    96			str[n - 1 - i] = tmp;
    97		}
    98	}
    99	
   100	int main(int argc, char *argv[])
   101	{
   102		char *filename = "stdin";
   103		size_t len, bufsiz = BUFSIZ;
   104		FILE *fp = stdin;
   105		int ch, rval = EXIT_SUCCESS;
   106	
   107		static const struct option longopts[] = {
   108			{ "version",    no_argument,       0, 'V' },
   109			{ "help",       no_argument,       0, 'h' },
   110			{ NULL,         0, 0, 0 }
   111		};
   112	
   113		setlocale(LC_ALL, "");
   114		bindtextdomain(PACKAGE, LOCALEDIR);
   115		textdomain(PACKAGE);
   116		atexit(close_stdout);
   117	
   118		signal(SIGINT, sig_handler);
   119		signal(SIGTERM, sig_handler);
   120	
   121		while ((ch = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
   122			switch(ch) {
   123			case 'V':
   124				printf(_("%s from %s\n"), program_invocation_short_name,
   125							  PACKAGE_STRING);
   126				exit(EXIT_SUCCESS);
   127			case 'h':
   128				usage(stdout);
   129			default:
   130				usage(stderr);
   131			}
   132	
   133		argc -= optind;
   134		argv += optind;
   135	
   136		buf = xmalloc(bufsiz * sizeof(wchar_t));
   137	
   138		do {
   139			if (*argv) {
   140				if ((fp = fopen(*argv, "r")) == NULL) {
   141					warn(_("cannot open %s"), *argv );
   142					rval = EXIT_FAILURE;
   143					++argv;
   144					continue;
   145				}
   146				filename = *argv++;
   147			}
   148	
   149			while (fgetws(buf, bufsiz, fp)) {
   150				len = wcslen(buf);
   151	
   152				/* This is my hack from setpwnam.c -janl */
   153				while (buf[len-1] != '\n' && !feof(fp)) {
   154					/* Extend input buffer if it failed getting the whole line */
   155					/* So now we double the buffer size */
   156					bufsiz *= 2;
   157	
   158					buf = xrealloc(buf, bufsiz * sizeof(wchar_t));
   159	
   160					/* And fill the rest of the buffer */
   161					if (!fgetws(&buf[len], bufsiz/2, fp))
   162						break;
   163	
   164					len = wcslen(buf);
   165				}
   166				if (buf[len - 1] == '\n')
   167					buf[len--] = '\0';
   168				reverse_str(buf, len);
   169				fputws(buf, stdout);
   170			}
   171			if (ferror(fp)) {
   172				warn("%s", filename);
   173				rval = EXIT_FAILURE;
   174			}
   175			fclose(fp);
   176		} while(*argv);
   177	
   178		free(buf);
   179		return rval;
   180	}

67行目のwchar_t *bufのbufという変数に、136行目と158行目でxmalloc/xreallocでメモリを確保し、149行目のfgetwsにて文字列を読み込んで、168行目のreverse_strにて左右反転させているようです。
reverse_strでは一文字ずつの反転になっているので、日本語でも正しくひっくり返るというわけですね。

また、このrev.cのソースを読んで分かるのが、fgetwsを使うためロケールでの使用文字かどうか確認できる点です。
LANGの設定を変更してrevの結果を確認してみます。

~$ echo $LANG
ja_JP.UTF8
~$ echo あいうえお > a.txt
~$ rev a.txt
おえういあ
~$ LANG=ja_JP.SJIS
~$ rev a.txt
rev: a.txt: Invalid or incomplete multibyte or wide character
~$ 

予想通り左右反転させようとしている文字列がLANGと食い違うため、エラーが発生しました。
次は、LANGの方はUTF8のままで、a.txtをSJISにして確認してみましょう。

~$ LANG=ja_JP.UTF8
~$ echo あいうえお | nkf -s > b.txt
~$ nkf --guess b.txt
Shift_JIS (LF)
~$ rev b.txt
rev: b.txt: Invalid or incomplete multibyte or wide character
~$ 

こちらも予想通りですが、現在のLANGと異なるためエラーとなりました。
では、UTF8のテキストに文字ではないコードを入れたらどうなるでしょうか。

~$ echo -e "あいうえ\x80お" > c.txt
~$ nkf --guess c.txt
EUC-JP
~$ cat c.txt
あいうえ?お
~$ rev c.txt
rev: c.txt: 無効または不完全なマルチバイトまたはワイド文字です

revコマンドではエラーとなりました。つまり、revコマンドを使えば、壊れた日本語(ワイド文字)テキストのチェックに使えることになります。
他の数値を埋め込んで確認してみます。

~$ echo -e "あいうえ\x11お" > d.txt
~$ rev d.txt
あ?えういあ

さきほど0x80を埋め込んだ時はエラーになったのに、0x11ではエラーになりませんでした。それもそのはず。0x11が、改行コードやタブも含まれている制御コードの範囲0x00〜0x1Fに含まれているからです。逆にこのコードが含まれているからといってエラーを返してはなりません。さらにASCIIコードが0x00〜0x7Fですので、同様にrev実行時にエラーを返してはなりません。

revで、文字コードチェックに使えることは分かりましたが、実際どのように使えば良いでしょうか。

revの結果でif文分岐できるのが理想でしょう。しかしrevはエラー発生時、標準エラー出力をしてしまいますので、このままでは出力が邪魔で使えません。加えて、標準出力の方も左右反転文字を使うわけではないため不要です。標準出力も標準エラー出力も非表示にする必要があります。
と言っても、邪魔な出力をしないようにすることは簡単です。出力先を画面ではなく、ファイルにしてしまえば良いからです。
まず、標準出力を画面に出力しないようにするには、

rev a.txt > trash.txt

分かりやすくtrash(ゴミ箱)というファイル名にしました。
では標準エラー出力も同じ要領でファイルにリダイレクトします。標準エラー出力をリダイレクトするには、2>という記述をします。

rev a.txt 2> trash.txt

次に、標準出力と、標準エラー出力、両者を合わせてリダイレクトします。

rev a.txt > trash.txt 2> trash.txt

一応これで画面上には表示されなくなりますが、trash.txtというファイルが作られてしまいます。回りくどくなりましたが、実はこのようなテンポラリファイルは必要ありません。データをただ捨てたい場合は、スペシャルファイルを使います。
/dev/nullは、リダイレクトすると何でも捨てられてしまうスペシャルファイルです。これを使ってみます。

rev a.txt > /dev/null 2> /dev/null

少しすっきりしました。
さあ、revをif分岐に使ってみましょう。確認のための関数を作成します。

~$ f(){
> if rev $1 > /dev/null 2> /dev/null;then
> echo OK;
> else
> echo NG;
> fi
> }

さきほど作成したファイル、a.txtが正しい日本語のファイル、c.txtが異常ファイルでした。念の為確認してみます。

~$ rev a.txt
おえういあ
~$ rev c.txt
rev: c.txt: 無効または不完全なマルチバイトまたはワイド文字です
~$ 

ではこれらのファイルを使って先ほどの関数のチェックをしてみます。

~$ f a.txt
OK
~$ f c.txt
NG

上手く分岐されたようです。
まだいろいろ確認することはありそうですが、revを使って日本語ファイルのチェックができそうな気がします。revの使い道が一つ増えましたね。

ちなみに、スペシャルファイルへリダイレクトする書き方として、
> /dev/null 2> /dev/null を、
1> /dev/null 2> /dev/null としても、
1> /dev/null 2>&1 としても、結果は同じです。

2 responses to “何に使うかわからないrevを使って文字コードチェック

  1. I must say you have hi quality posts here. But your page is not even in TOP 20 google’s search results.
    I can help you to rank your website, send me email if you are interested – philzendia@o2.pl

  2. That is the very best search system in the planet

Leave a Reply

Your email address will not be published. Required fields are marked *

CAPTCHA