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

6-1.比較・差分

(Linuxコマンドの一覧はコチラ 中級者のためのLinuxコマンド入門)

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

takk~$ apt source util-linux
takk~$ cat -n util-linux-2.33.1/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	static void sig_handler(int signo __attribute__ ((__unused__)))
    68	{
    69		_exit(EXIT_SUCCESS);
    70	}
    71
    72	static void __attribute__((__noreturn__)) usage(void)
    73	{
    74		FILE *out = stdout;
    75		fprintf(out, _("Usage: %s [options] [file ...]\n"),
    76			program_invocation_short_name);
    77
    78		fputs(USAGE_SEPARATOR, out);
    79		fputs(_("Reverse lines characterwise.\n"), out);
    80
    81		fputs(USAGE_OPTIONS, out);
    82		printf(USAGE_HELP_OPTIONS(16));
    83		printf(USAGE_MAN_TAIL("rev(1)"));
    84
    85		exit(EXIT_SUCCESS);
    86	}
    87
    88	static void reverse_str(wchar_t *str, size_t n)
    89	{
    90		size_t i;
    91
    92		for (i = 0; i < n / 2; ++i) {
    93			wchar_t tmp = str[i];
    94			str[i] = str[n - 1 - i];
    95			str[n - 1 - i] = tmp;
    96		}
    97	}
    98
    99	int main(int argc, char *argv[])
   100	{
   101		char const *filename = "stdin";
   102		wchar_t *buf;
   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,       NULL, 'V' },
   109			{ "help",       no_argument,       NULL, 'h' },
   110			{ NULL,         0, NULL, 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(UTIL_LINUX_VERSION);
   125				exit(EXIT_SUCCESS);
   126			case 'h':
   127				usage();
   128			default:
   129				errtryhelp(EXIT_FAILURE);
   130			}
   131
   132		argc -= optind;
   133		argv += optind;
   134
   135		buf = xmalloc(bufsiz * sizeof(wchar_t));
   136
   137		do {
   138			if (*argv) {
   139				if ((fp = fopen(*argv, "r")) == NULL) {
   140					warn(_("cannot open %s"), *argv );
   141					rval = EXIT_FAILURE;
   142					++argv;
   143					continue;
   144				}
   145				filename = *argv++;
   146			}
   147
   148			while (fgetws(buf, bufsiz, fp)) {
   149				len = wcslen(buf);
   150
   151				if (len == 0)
   152					continue;
   153
   154				/* This is my hack from setpwnam.c -janl */
   155				while (buf[len-1] != '\n' && !feof(fp)) {
   156					/* Extend input buffer if it failed getting the whole line */
   157					/* So now we double the buffer size */
   158					bufsiz *= 2;
   159
   160					buf = xrealloc(buf, bufsiz * sizeof(wchar_t));
   161
   162					/* And fill the rest of the buffer */
   163					if (!fgetws(&buf[len], bufsiz/2, fp))
   164						break;
   165
   166					len = wcslen(buf);
   167				}
   168				if (buf[len - 1] == '\n')
   169					buf[len--] = '\0';
   170				reverse_str(buf, len);
   171				fputws(buf, stdout);
   172			}
   173			if (ferror(fp)) {
   174				warn("%s", filename);
   175				rval = EXIT_FAILURE;
   176			}
   177			fclose(fp);
   178		} while(*argv);
   179
   180		free(buf);
   181		return rval;
   182	}
   183
takk~$

102行目のwchar_t *bufのbufという変数に、
135行目と160行目でxmalloc/xreallocでメモリを確保し、
148行目のfgetwsにて文字列を読み込んで、
170行目のreverse_strにて左右反転させています。

reverse_strでは一文字ずつの反転になっているので、日本語でも正しくひっくり返るというわけですね。

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

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

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

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

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

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

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

takk~$ echo -e "あいうえ\x11お" > d.txt
takk~$ 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分岐に使ってみましょう。確認のための関数を作成します。

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

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

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

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

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

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

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

コメント

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