Sample Site

GAWK コマンドプロンプトで column -t

column

Linux系のユーティリティツール column は、データを複数列に揃えて表示するものです。中でも csv 等のデータファイルについて -t オプションによるテーブル表示が特に便利です。

column の win32 版をネットで探しましたが、シェル(ターミナルエミュレータ)に結び付いていない、コマンドプロンプト上で使えるものは見つかりませんでした。GNU win32 や util-linux-ng for Windows 等にありそうなのですが、ないんですよね。

MSYS2 付属の column の動作は以下の通りです。

UTF-8/MSYS2/内蔵 column.exe column_02.jpg

データが整列すると、生の csv と比べて格段に見やすくなります。


偽 column for cmd

上記の動作を模倣します。あくまでも column の機能の中でも、テーブル(-t)の機能だけの模倣で、その他の機能は含んでいません。また、画面からあふれる等の問題点は考慮していませんので、悪しからず。

column.txt (入力テキスト)

1,石川卓也,イシカワタクヤ,男,タッキー,25,A,999 2,佐藤ゆかり,サトウユカリ,女,ユカ,20,AB,454 3,鈴木ヒカル,すずきひかる,男,ひかりん,18,O,268 4,大野仁,オオノヒトシ,男,オオノ,26,B,849 5,広瀬雄之助,,男,ヒロセ,46,A,500 6,MichaelAndersen,ミヒャエルアンデルセン,男,ミカ,7,AB,1200 7,東徹,ヒガシトオル,男,トオル,22,B,651 8,AnnaHope,アンナホープ,女,アンナ,30,O,570 9,江川智弘,エガワトモヒロ,男,トモ,22,A,278 10,戸塚洋二,トヅカヨウジ,男,ヨウチャン,19,A,38

column.bat

コマンドをバッチファイルとして登録します。このファイルはパスの通ったディレクトリに置きます。コマンドのためのディレクトリを作成してパスを通しておくと重宝します。

1 : @echo off 2 : rem column C:\BATCHFILES\ 3 : if "%~2"=="" ( 4 : rem usage: something command | column [","] 5 : gawk414 -v arg1=%1 -f %~dp0column.awk | more 6 : ) else ( 7 : rem usage: column "," infile 8 : gawk414 -v arg1=%1 -f %~dp0column.awk %2 | more 9 : )

簡易的ですが、コマンド + 区切り文字 + ファイル名 で起動するか、パイプ(標準入力)から起動するかを、第二引数の有無で決まるようにしています。日本語表示でつまづかないように(致命的エラー)、[more] は今回必須です。余計な機能ですが、ページがいっぱいになると more が働きます。


column.awk

2次元の配列を使用しますので、GAWK4以上で動作します。上記バッチファイルと同じディレクトリに配置します。

BEGIN
1 : #. BEGIN: 初期化 2 : BEGIN { 3 : _asc_init(); 4 : SPC = 2; #フィールド最長値との間隔 5 : if (arg1) FS = arg1; #コマンド引数 6 : }
ACTION_01
7 : #. ACTION_01: フィールド表示長と値の格納 8 : { 9 : for (i = 1; i <= NF; i++) { 10 : val[NR][i] = $i; #フィールド値 11 : num = len[NR][i] = blength($i); #フィールド値の表示長 12 : if (max[i] < num) max[i] = num; #最長値:max[i] 13 : ($i == $i + 0 || !$i) ? 0 : flg[i] = 1; #非数値判定 14 : } 15 : }
END
16 : #. END: 整形出力 17 : END { 18 : ct = length(max); #フィールド数 19 : for (i = 1; i <= FNR; i++) { 20 : for (j = 1; j <= ct; j++) { 21 : if (flg[j]) { #非数値(左寄せ) 22 : printf("%s", val[i][j]); 23 : printf("%*s", -(max[j] - len[i][j] + SPC), ""); #空白追加 24 : } 25 : else { #数値(右寄せ) 26 : printf("%*s", max[j], val[i][j]); 27 : printf("%*s", SPC, ""); 28 : } 29 : } 30 : print ""; #改行 31 : } 32 : }
_asc_init()
33 : #. _asc_init():ASCII+半角カナ辞書(Shift_JIS) _asc["ル"] 34 : # 戻値: 35 : function _asc_init( i, hk, ar, qt) { 36 : for (i = 0; i < 128; i++) _asc[sprintf("%c", i)] = i; 37 : hk = "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚"; 38 : qt = split(hk, ar, ""); 39 : for (i = 1; i <= qt; i++) _asc[ar[i]] = 160 + i; #Shift_JIS 40 : _SCLP = " "; #マルチバイト文字の断片を表す文字 41 : }
blength()
42 : #. blength():文字列長さ疑似バイトを返す(辞書_asc) 43 : # 戻値: 文字列表示長さ 44 : # str:in: 検査文字列 45 : function blength(str, i, ch, lenb) { 46 : while (ch = substr(str, ++i, 1)) 47 : (ch in _asc) ? lenb += 1 : lenb += 2; 48 : return lenb + 0; 49 : }

END セクションで非数値/数値の判定のために flg[j] を参照します。そもそも数値フィールドは flg[j] に要素を持っていません。実行時のループ初回に flg[j]="" が作成され、以降ずっと偽となり、数値フィールドと判定されます。

実行結果

コマンドプロンプト/Shift_JIS column_01.jpg

数値だけのフィールドは、パディング(右寄せ)するようにしました。空欄("")は判定しませんが、1つでも非数値があると、そのフィールド全体を非数値であるとします。

MSYS2/bash/.bashrc

1 : #column_t 2 : function column_t() { 3 : if [ $# -lt 2 ]; then 4 : gawk -v arg1=$1 -f /C/BATCHFILES/column_u8.awk 5 : else 6 : gawk -v arg1=$1 -f /C/BATCHFILES/column_u8.awk $2 7 : fi 8 : }

.bashrcに関数を追加するとコマンドプロンプト(Shift_JIS)と同様にMSYS2/UTF-8でも使えるようになります。UTF-8のテキストとスクリプトファイルが必要です。こちらには column がありますので、あまり意味はありませんが、UTF-8での動作確認のために使用します。

MSYS2/UTF-8/function column_t column_03.jpg

:

:

:

:

:

高速化のために、フィールド値を保存せず、表示長は手製のエクステンション(Shift_JIS専用)を使って求めるバージョンを作成してみました。 getline で先読みすると、標準入力(パイプから)は利用できなくなるようですので、素直に GAWK が直接ファイルを読むタイプに変更しました。/dev/stdin は揮発してしまうのでしょうか? getline 後のアクションが動きません。この手の込み入った内容は調査するのが大変です。

1 : @echo off 2 : rem column_f 簡易整形出力(テーブル) 3 : rem usage: column "," file 4 : setlocal 5 : set delm=%1 6 : set file=%~f2 7 : pushd %~dp0 8 : gawk414 -f column_f.awk %delm% %file% | more 9 : popd 10 : endlocal
1 : # Shift_JIS 専用高速バージョン 2 : @load "win_sjis2.dll" 3 : #. BEGINFILE: エラー回避 4 : BEGINFILE { 5 : if (ERRNO) nextfile; 6 : } 7 : #. BEGIN: 初期化 8 : BEGIN { 9 : SPC = 1; #フィールド最長値との間隔 10 : FS = ARGV[1]; 11 : file = ARGV[2]; 12 : if (!file) exit; 13 : while (getline < file > 0) { 14 : c++; 15 : for (i = 1; i < NF; i++) { 16 : f[c][i] = lengthb($i); 17 : if (m[i] < f[c][i]) m[i] = f[c][i]; #最長値:m[i] 18 : } 19 : } 20 : close(file); 21 : } 22 : #. ACTION_01: 整形出力 23 : FILENAME == ARGV[2] { 24 : for (i = 1; i < NF; i++) { 25 : printf("%s", $i); 26 : printf("%*s", -(m[i] - f[NR][i] + SPC), ""); 27 : } 28 : print $NF; #各フィールドの末尾の値 29 : }

速度計測 (同様なテキスト1万行、結果をリダイレクト)

MSYS2にて real user sys charset
1.上部(blength) 0.735 0.608 0.091 UTF-8
2.エクステンション 0.319 0.265 0.030 Shift_JIS
3.MSYS2 column 0.302 0.233 0.015 UTF-8

すべて整形は完璧でした。エクステンションは MSYS2 付属の column に追い着きませんでした。UTF-8は原理的に上部のソース(blength)と同じく文字列の先頭から表示長を数えないといけませんので、効率が悪いはずなんですが、やはり、ポインタのせいでしょうか。それとも、cat + パイプで渡せない仕様が悪いのか...本家の column はもっと早いのかもしれません。というより、余計な機能(数値フィールド右寄せ)を組み込んだため、ものすごく遅いんじゃないか、と思っていたプロトタイプ(上部ソース)が、MSYS2 column.exe の2.6倍(user)と大健闘していていることに、少し驚いています。

後日 linux mint にて追試。付属の column は 0.046s(user)ととても速かったのですが、null データをスキップしてしまうため、使えません。-e オプション?で無視しなくなるように man に書いてあるけれど、効きません。util-linux 系の column でも数値右寄せは自動とはいかず、列を指定(オプション)しないといけないようです。linux については、以下のサイトがとても参考になります。

JM project(Japan) URL   https://linuxjm.osdn.jp/