GAWK 文字列をバイト単位で数える方法

 一つのスクリプトの中で、文字列のキャラクタ数とバイト( 半角換算 )両方を取得できると、何かと便利なのですが、そのようなソースを掲載されているサイトがありません。

 というわけで作りました。

 仕組みは単純です。半角( Shift_JISの1バイト )として数えるべき文字の集合を、辞書としてBEGIN先頭で初期化し、blength(str)という関数を用いて、str を一文字ずつこの辞書と照合して、半角を 1 、それ以外を 2 としてカウントし、合計結果を返します。半角辞書にはASCIIに規定されるすべての文字・制御文字が含まれますので、エスケープシーケンスはもちろん、しばしば使用されるSUBSEP( 0x1C )もキチンと認識します。 改行文字( \n )は、Windows ではCRLF( 0D0A )ですが、1 半角文字 ( 0A ) としてカウントします。

 処理時間はだいたい length() の 13 倍くらいかかるようです(筆者の環境)。単発で使用する場合、全半角混在 1000 文字程度では time.dll を使用しても測定できない速度ではありますが、for ループの中で使用すると、かなり重たい処理となります。

注:Windows / Shift_JIS 環境でのみ動作確認を行っております。


Homeへ戻る
  1. Goto BEGIN
  2. Goto _asc_init()
  3. Goto blength()
#. BEGIN;blength()の検証 Shift_JIS専用 @load "time"; @load "lengthb"; BEGIN { _asc_init(); print "length()とblength()の比較\n" str = "こんにちは世界!hello,world!,ハローワールド!"; ret1 = length(str); ret2 = blength(str); printf("str=%s\n", str); printf(":length(str)=%d\n:blength(str)=%d\n\n", ret1, ret2); #大量の文字列 全半角混合300万字 経過時間測定 print "str * 100,000 + NUL文字"; str = str str str str str str str str str str; #*10 str = str str str str str str str str str str; #*100 str = str str str str str str str str str str; #*1000 str = str str str str str str str str str str; #*10000 str = str str str str str str str str str str; #*100000 str = str str str str str str str str str str "\0"; #*1000000 #length() stime = gettimeofday(); ret1 = length(str); etime = gettimeofday(); printf(":length():elapsed_time=%d(msec)\n", (etime - stime) * 1000); #blength() stime = gettimeofday(); ret2 = blength(str); etime = gettimeofday(); printf(":blength():elapsed_time=%d(msec)\n", (etime - stime) * 1000); printf(":length(str)=%d\n:blength(str)=%d\n\n", ret1, ret2); #参考:lengthb.dll print "参考 自作lengthb.dll" stime = gettimeofday(); ret3 = lengthb(str); etime = gettimeofday(); printf(":lengthb.dll:elapsed_time=%d(msec)\n", (etime - stime) * 1000); printf(":lengthb(str)=%d\n", ret3); print "\n"; #見えない文字 print "見えない文字(SUBSEPとベル[\\a])" str = "Hello" SUBSEP "\a" "こんにちは"; printf("str=%s\n", str); ret1 = length(str); ret2 = blength(str); printf(":length(str)=%d\n:blength(str)=%d\n", ret1, ret2); }#BEGIN
#. _asc_init();参照用配列 _asc[] の初期化 (+Shift_JIS半角カタカナ) # 大域変数_asc[]を作成 ASCIIキャラクタをkey、ASCIIコード10進をelemとする function _asc_init( i, ch, hk, ar, re) { for (i = 0; i < 128; i++) { ch = sprintf("%c", i); _asc[ch] = i; } hk = "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソ\ タチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚"; re = split(hk, ar, ""); for (i = 1; i < re + 1; i++) { _asc[ar[i]] = 160 + i; } }
#. blength();文字列長さを疑似バイトで数える _asc_init()に依存 function blength(str, lenb, len, i) { len = length(str); for (i = 1; i < len + 1; i++) { (substr(str, i, 1) in _asc) ? lenb += 1 : lenb += 2; } return lenb; }

実行結果: 時間計測のためgawk4を使用しました。

length()とblength()の比較 str=こんにちは世界!hello,world!,ハローワールド! :length(str)=30 :blength(str)=37 str * 100,000 + NUL文字 :length():elapsed_time=122(msec) :blength():elapsed_time=1548(msec) :length(str)=3000001 :blength(str)=3700001 参考 自作lengthb.dll :lengthb.dll:elapsed_time=0(msec) :lengthb(str)=3700001 見えない文字(SUBSEPとベル[\a]) str=Helloこんにちは :length(str)=12 :blength(str)=17

APPENDIX


 本論でも少し登場しましたが、GAWK4 for win32 のための lengthb.dll というものを作成しました。 lengthb.dll には、本当の意味で文字列長さをバイトで返す lengthb() という関数が入っています。

 GAWK4 以降は拡張として DLL を利用できます。win32用 GAWK4 のソースを入手し、extension フォルダにある諸先生方が書かれた素晴らしいお手本をもとに、コーディングしてみました。4/5 はコピペです。

win32用 GAWK4 のソースはこちらの ezwinports さんから入手することができます。

お知らせ

 2017/10 GAWK ver.4.2.0 がリリースされたようです。ページ末尾にある ダウンロード用 DLL は ver.4.1.4 までしか適合していません。また、ver.4.2.0 では DLL の登録関数及びdo_***関数の引数が変わりましたので、ソースの方もコンパイル/リンクできません。ver.4.2以降の安定度合いが確認できたなら、おもむろにソースを更新します。

lengthb.c ( lengthb.dllのソース )

1 : /* lengthb.c*/ 2 : 3 : #ifdef HAVE_CONFIG_H 4 : #include <config.h> 5 : #endif 6 : 7 : #include <stdio.h> 8 : #include <assert.h> 9 : #include <stdlib.h> 10 : #include <string.h> 11 : #include <unistd.h> 12 : 13 : #include <sys/types.h> 14 : #include <sys/stat.h> 15 : 16 : #include "gawkapi.h" 17 : 18 : #include "gettext.h" 19 : #define _(msgid) gettext(msgid) 20 : #define N_(msgid) msgid 21 : 22 : static const gawk_api_t *api; /* for convenience macros to work */ 23 : static awk_ext_id_t *ext_id; 24 : static const char *ext_version = "lengthb extension: version 1.0"; 25 : static awk_bool_t (*init_func)(void) = NULL; 26 : 27 : int plugin_is_GPL_compatible; 28 : 29 : /* do_lengthb --- return numeric value of length of string as byte*/ 30 : 31 : static awk_value_t * 32 : do_lengthb(int nargs, awk_value_t *result) 33 : { 34 : awk_value_t tstr; 35 : double ret = -1; 36 : 37 : assert(result != NULL); 38 : 39 : if (do_lint && nargs > 1) 40 : lintwarn(ext_id, _("lengthb: called with too many arguments")); 41 : 42 : if (get_argument(0, AWK_STRING, & tstr)) { 43 : ret = tstr.str_value.len; 44 : } else if (do_lint) { 45 : if (nargs == 0) 46 : lintwarn(ext_id, 47 : _("lengthb: called with no arguments")); 48 : else 49 : lintwarn(ext_id, 50 : _("lengthb: called with inappropriate argument(s)")); 51 : } 52 : 53 : /* Set the return value */ 54 : return make_number(ret, result); 55 : } 56 : 57 : static awk_ext_func_t func_table[] = { 58 : { "lengthb", do_lengthb, 1 }, 59 : }; 60 : 61 : /* define the dl_load function using the boilerplate macro */ 62 : 63 : dl_load_func(func_table, lengthb, "")

このソースは tenk* さんの c2htm v1.72 で html 化しています

 gawkapi.h に書かれていましたが、GAWK4 とDLL間は汎用のコンテナみたいな構造体、 "awk_value_t" によってやり取りを行っているようです。文字列の受け渡しに利用されているのは、そのコンテナに内包される共用体の一つ "awk_string_t" という、文字列へのポインタと、文字列長さ( バイトサイズ )で構成される構造体でした。今回作成した DLL の lengthb() は文字列長さ( バイトサイズ )をそのまま返します。Shift_JIS 環境においては極めて好都合です。


gawkapi.h(抜粋)

typedef struct awk_string { char *str; /* data */ size_t len; /* length thereof, in chars */ } awk_string_t; typedef struct awk_value { awk_valtype_t val_type; union { awk_string_t s; double d; awk_array_t a; awk_scalar_t scl; awk_value_cookie_t vc; } u; #define str_value u.s #define num_value u.d #define array_cookie u.a #define scalar_cookie u.scl #define value_cookie u.vc } awk_value_t;

 lengthb.c から DLL を作成する手順です。
必要なものは MINGW の gcc と win32用 GAWK4 のソースです。
lengthb.c を上記 GAWK4 ソースのフォルダ( gawkapi.h があるところ)にコピーします。
コマンドプロンプトを起動し、cd コマンドでGAWKソースフォルダに移り、下記のように中間ファイルと DLL を作成します。1行目を実行すると中間ファイル lengthb.o が作成され、2行目を実行すると中間ファイルから DLL ファイルを作成します。

MINGW gcc コマンドプロンプト

mingw32-gcc -c lengthb.c mingw32-gcc -shared -o lengthb.dll lengthb.o

あとは、作成したdllファイルを任意のフォルダに移動させ、GAWK にこのlengthb.dllの位置を知らせ、実行します。 バッチファイルにて、

batファイル

set AWKLIBPATH=「作成したlengthb.dllの入っているフォルダの絶対パス」
gawk -l "lengthb" -f hoge.awk


などとします。
コマンドオプション -l "lengthb" の代わりに、hoge.awkのソースに、

hoge.awk

@load "lengthb";
BEGIN{


と書いても良いです。というか、こう書くべきです。

 Windowsでパッケージ化された GAWK4 を使う場合、DLL が付属していないので、詳しい方でないと使う機会がありません。加えて AWKLIBPATH の存在を覚えておかないと、DLL が使えないのです。筆者もわからなくてネット上を彷徨いました。結局システム環境変数にAWKLIBPATHを登録することで事なきを得ました(バッチファイルでPATHを通す必要がなくなる)が、DLLの使い方 / 作り方も含めて、総合的な Windows 用 GAWK の日本語マニュアルがあるといいなぁとつくづく思いました。

 さて、検証です。 漢字アルファベット混合の文字列サイズ「2億バイト」で検証しました。 あくまでも、筆者の環境であることと、計測には誤差が生じることをご了承ください。 一応同条件で5回計測しています。
標準のlength()は、systime()で5秒でした。1億3000万字。
lengthb.dllを使用した場合、systime()では、0秒です。2億カウント(バイト)。
time.dllを使っても0(msec)です。当然です、サイズがわかっているのですから。
このページで紹介しましたblength()ではこれも当然というべきか、systime()で 62秒かかりました。2億カウントです。

 前二者と比較すると大変時間のかかるblength()ですが、しかしながら、敢えてオーバーヘッドを無視して単純計算すると、このblength()は、1(msec)あたり、ASCII他と照合しながら2096文字数える仕事をしているわけですので、まずまず優秀です。現在の GAWK は、日本語について半角単位で数えるなどということを想定していないのですから、許容せざるを得ないコスト( length() の約13倍 )と考えています。それにしても、数えられるだけマシですよね。

 ともあれ、GAWK で DLL を使う / 作る意義をおおいに見出せる結果でした。


 興味のある方はこちら(ダウンロード8K)に DLL を 置いておきますので、ダウンロードしてください。ただし使用にあたっては、Windows / Shift_JIS環境以外では使えないこと(純粋に文字列のbytesを返すので)、全て自己責任であることをお忘れなく。


ページのトップへ戻る
Homeへ戻る