複数のファイルをマージする

1.概要

パソコンで色んなデータを扱う場合、複数ファイルの連結(マージ)する作業は比較的多いと思います。 筆者の場合は、Excel VBAやPythonなどで作成したスクレイピングプログラムでWeb上から収集したデータを CSVなどに出力して加工する場合も多いです。このようなデータを蓄積して1つのファイルに連結(マージ)後、 DB(データベース)を新規作成して、初期インポート用のファイルとして利用しています。 定期的に決まったフォーマットで同じ作業のものは、簡単なバッチファイル(bat)を作って実行させています。 バッチファイルは、WMI(Windows Management Instrumentation)やPower Shellの登場で枯れた技術のように 思われがちですが、企業のIT部門などでは、まだまだ、広く利用されていて現役で活躍しています。 手軽で簡単であり、高速性を求めなければ便利ですね。ただ、会社なとでバッチファイルを他の人に使わせようとした時に、 マージ対象とするファイルやディレクリ―が変更となった場合やマージ後の出力ファイル名が他のシステムと 関連していたりすると、当然変更が必要となるため、その都度、作成者が面倒を見ることになります。 そのような事情から、他の人が使うことを前提とした場合は、GUIで操作画面がある実行形式(exe)のツール があった方が便利です。今回は、筆者が作成したマージツールを紹介します。

2.ファイルマージツール

2-1.ソースコード

概要で説明しましたが、バッチファイルによる簡単なサンプルを載せておきます。下記のサンプルですが、 必要に応じて、9行目のタイトル名と17行目の処理対象ファイルの拡張子(ワイルドカード)とマージ後の出力ファイル名 を適時修正して利用して下さい。マージ対象ファイルが格納されているディレクトリーは指定していませんので、 追加するか、同一ディレクリ―にバッチファイルを置いて実行させて下さい。どこにでもある簡単なものですみません。 雛形として利用して頂けたらと思います。バッチの命令としては、type命令copy命令で同等の処理ができますが、 copy命令の方がやや高速に処理できるようです。

テキストファイルのマージ用バッチサンプル(txtmarge.bat)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@echo off
setlocal
cd /d %~dp0
 
echo.
echo.
echo ***********************************************************
echo.
echo    CSVファイルのマージ処理
echo.
echo.
echo ***********************************************************
echo.
echo.
 
rem ***** マージ開始 *****
copy *.csv > CSVマージ済み.csv
 
echo 処理が完了しました。何かキーを押してください。
echo.
 
pause >nul
endlocal
exit

続いて紹介するのが、テキストファイルマージツールです。プログラム言語は、HSP(Hot soup Processer)3.51で 作成しています。HSPとは、20年以上前からサポートが続けられて進化している国産のスクリプト言語です。 最近では、M-1グランプリで優勝したマジカルラブリーの野田クリスタルさんが自作ゲームの開発に利用していることが 話題となり、再び注目されています。HSPに興味がありましたら、下記の公式サイトを覗いてみて下さい。 筆者もちょつとしたツールやアクセサリーなどを開発するのに20年以上前から愛用しています。

プログラミング言語HSP公式サイト : HSPTV
URL : http://hsp.tv/index2.html

下記のリストは、今回紹介するテキストファイルマージツール(tfmarge)のHSPソースコード(HSPユーザー向け)です。 HSPの開発環境を構築している人は、HSPのスクリプトエディタに読み込み、実行して見て下さい。 HSPの開発環境をインストールしていない場合は、HSP公式サイトから入手して下さい。

マージ処理は、bload命令とbsave命令によるファイルのオフセット値を指定しての読み込みと書き込み による処理のため、比較的高速な処理が実現できています。一応、バイナリーデータもマージ可能です。
テキストファイルマージ(tfmarge.hsp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
  ;***** テキストファイルマージ (tfmarge.hsp) *****
   
  ;***** 実行ファイル自動作成 ****
  #packopt type 0
  #packopt name "tfmarge"
  #packopt runtime "hsprt"
  #packopt hide 1
 
  ;***** notepad命令形式文字列ソート *****
  #module
  #uselib "kernel32.dll"
  #cfunc lstrcmp "lstrcmpA" int,int
   
  #deffunc ntsort var buf,int flag
  sdim buf2,99999
  notesel buf : max=notemax
  repeat max
  sdim temp,256
  notesel buf : noteget temp,cnt
  repeat max
    sdim temp2,256
    notesel buf2 : noteget temp2,cnt
    if temp2="" : noteadd temp,cnt : break
    ret=lstrcmp(varptr(temp),varptr(temp2))
    if flag=0{
      if ret<0 { ;昇順
      noteadd temp,cnt
      break
      }
    }else{
      if ret>0 { ;降順
      noteadd temp,cnt
      break
      }
    }
    loop
  loop
  buf=buf2
  return
  #global
 
  #ifndef xdim
  #uselib "kernel32.dll"
  #func global VirtualProtect@_xdim "VirtualProtect" var,int,int,var
  #define global xdim(%1,%2) dim %1,%2: VirtualProtect@_xdim %1,%2*4,$40,x@_xdim
  #endif
 
  #module
  #uselib "ole32.dll"
  #func  CoTaskMemFree "CoTaskMemFree" int
  #uselib "shell32.dll"
  #cfunc SHBrowseForFolder "SHBrowseForFolderA" int
  #cfunc SHGetPathFromIDList "SHGetPathFromIDListA" int,int
  #uselib "user32.dll"
  #func  SendMessage "SendMessageA" int,int,int,int
 
  #deffunc BrowseFolder str _szTitle, str _defaultfolder
    szTitle = _szTitle : inifldr = _defaultfolder : sdim retfldr, 260 : xdim fncode, 8
    fncode = $08247c83,$8b147501,$ff102444,$68016a30,$00000466,$102474ff,$330450ff,$0010c2c0
    hbdata = varptr(inifldr), varptr(SendMessage)
    BROWSEINFO = hwnd, 0, varptr(retfldr), varptr(szTitle), 3, varptr(fncode), varptr(hbdata), 0
    pidl = SHBrowseForFolder(varptr(BROWSEINFO))
    fret = SHGetPathFromIDList(pidl,varptr(retfldr))
    CoTaskMemFree pidl
    mref stt,64 : stt = fret
  return retfldr
  #global
 
  #uselib "comctl32.dll"
  #func  InitCommonControlsEx "InitCommonControlsEx" sptr
  #cfunc CreateStatusWindow "CreateStatusWindow" int,sptr,int,int
  #uselib  "user32.dll"
  #func  DestroyWindow "DestroyWindow" int
  #func  InvalidateRect "InvalidateRect" int,int,int
  #func  GetWindowRect "GetWindowRect" int,var
 
  #define VER_TFAPPRI 1.0
  #define MAXPATH   260
  #define CB_GETLBTEXT    $00000148 
  #define EM_SETMARGINS $000000D3
  #define EC_LEFTMARGIN $00000001
  #define EC_RIGHTMARGIN  $00000002
  #define SB_SETTEXT    $00000401
  #define ctype MAKELONG(%1,%2) (%1&$ffff|(%2<<16))
 
  sdim instdir, MAXPATH
  sdim genename,MAXPATH
  sdim Folder,  MAXPATH
  sdim tmpstr,64
  sdim s,1024*5
  sdim buf
  sdim cb
  sdim temp
  sdim outname,48
   
  ;***** 起動ディレクトリ取得 *****
  if hspstat&1=0 { instdir=dirinfo(1)+"¥¥" : chdir dirinfo(1)
  } else {
    instdir=dirinfo(0)+"¥¥"
  }
  ;***** GUI画面作成 *****
  screen 0,580,200,0
  sysfont 17  : syscolor 8
  BarName="テキストファイルマージ Ver"+strf("%.1f",VER_TFAPPRI)+"  (tfmarge)"
  title BarName
  font "Meiryo UI",12,0 : objmode 2
  color 0,0,0 : pos 11,15 : mes "対象先"
  pos 55,11 : input Folder,400,20
  sendmsg objinfo(0,2), EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN,MAKELONG(2,0)
  objsize 46,24 : pos 459,8: button "参照", *dirset
  wcard="*.*¥n*.txt¥n*.dat¥n*.csv¥n*.htm¥n*.html¥n*.css¥n*.xml¥n*.log"
  pos 11,43 : mes "拡張子"
  font "Meiryo UI",14,0 : objmode 2
  n=1 : objsize 80,24 : pos 55,38: combox n,120,wcard
  font "Meiryo UI",12,0 : objmode 2
  pos 150,43 : mes "出力ファイル名"
  font "Meiryo UI",12,0 : objmode 2
  pos 235,40 : input outname,220,20
  sendmsg objinfo(3,2), EM_SETMARGINS, EC_LEFTMARGIN|EC_RIGHTMARGIN,MAKELONG(2,0)
  objsize 46,24 : pos 459,37: button gosub "自動", *autoname
  objsize 60,50 : pos 510,11: button "マージ", *marge
 
  ;***** StatusBar object setting *****
  gosub *statusGen
  gosub *statusMsg
  gosub *statusstring
 
  gsel 0,1
  ;***** 割込み処理 *****
  onexit goto  *owari
  onclick gosub *msgclr
  stop
 
*dirset
  ;***** フォルダ選択ダイアログを起動 *****
    BrowseFolder "フォルダを選択してください", dir_cur
    if stat {
        Folder = refstr+"¥¥"
    }
    objprm 0,Folder
  stop
 
*autoname
  ;***** 日付時刻をファイル名と仮設定する *****
  outname =str(gettime(0))
  outname+=strf("%02d",gettime(1))
  outname+=strf("%02d",gettime(3))
  outname+=strf("%02d",gettime(4))
  outname+=strf("%02d",gettime(5))
  outname+=strf("%02d",gettime(6))
  ;コンボボックスの選択文字列を取得(拡張子)
  sendmsg objinfo(2,2),CB_GETLBTEXT,n,varptr(temp)
  p=strlen(temp)
  outname+=strmid(temp,1,p-1)
  objprm 3,outname
  return
 
*statusMsg
  ;***** ステータスバー初期メッセージ *****
  String=" ファイルマージ(連結)する対象先ディレクトリを指定して下さい。"
  return
 
*statusGen
  ;***** ステータスバーを作成 *****
  InitCommonControlsEx icx : String=""
  stshwnd=CreateStatusWindow($50000803,String,hwnd,0)
  InvalidateRect stshwnd,0,1
  dim rc,4 : GetWindowRect stshwnd,rc : sh = rc(3)-rc(1)
  return
 
*statusstring
  ;***** ステータスバーに文字列を設定 *****
  sendmsg stshwnd,SB_SETTEXT,0,varptr(String)
  return
 
*msgclr
  ;***** メッセージクリア *****
  color 255,255,255 : boxf 19,109,320,122 : color 0,0,0
  return
 
*marge
  ;***** マージ処理開始 *****
  gosub *msgclr
  if Folder="" {
    dialog "ファイルマージする対象フォルダを指定して下さい。",0
    stop
  }
  chdir Folder
  ; 指定ディレクトリのファイル名のみを取得
  sendmsg objinfo(2,2),CB_GETLBTEXT,n,varptr(cb)
  dirlist s,cb,1 : num=stat
   
  ; ファイル名を五十音別に昇順ソート
  ntsort s, 0
  ; 出力ファイルの空の実体を作成する
  notesel buf
  if outname="" {
    String=" 出力ファイル名が省略されたので、日付時刻のファイル名を仮設定しました。"
    gosub *statusstring
    gosub *autoname
  }
  genename=Folder+outname
  notesave genename
  notesel s
   
  ; 1個目のファイルをマージする
  noteget tmpstr,0
  exist Folder+tmpstr
  if strsize=-1 {
    dialog "指定したディレクトリにはファイルがありません。",0 : stop
  }
  sdim filebuf,strsize
  bload Folder+tmpstr,filebuf,strsize
  bsave genename,filebuf,strsize,0
  offset=strsize
 
  ; n個目のファイルをマージする
  repeat num,1
    noteget tmpstr,cnt
    exist Folder+tmpstr : sdim filebuf,strsize
    if strsize=-1 : break
    bload Folder+tmpstr,filebuf,strsize
    bsave genename,filebuf,strsize,offset
    fsize =strsize : offset+=fsize
  loop
  exist genename
  pos 20,110 : mes "ファイルのマージ処理が完了しました。"+"   "+strsize+"byte"
  outname = ""
  gosub *statusMsg
  gosub *statusstring
  stop
   
*owari
  ;***** 終了処理  *****
  onexit 0
  DestroyWindow stshwnd
  end

2-2.ツールの使い方

テキストファイルマージツール(tfmarge)の使い方です。ツールをダウンロードしたら、任意の場所で解凍して下さい。
    (1)tfmarge.exeをダブルクリックして起動させます。
    (2)連結(マージ)対象のファイルが格納されているディレクリ―を参照ボタンをクリックして
         選択します。
    (3)拡張子をプルダウンよりクリックして選択します。
    (4)マージ後の出力ファイル名(拡張子含む)を入力します。自動ボタンをクリックすると
         日付時刻形式のファイル名が作成されます。※省略時も同じです。
    (5)マージボタンをクリックしてマージ処理を実行します。
    (5)マージ対象ファイル数や容量にも依りますが、比較的高速に処理されます。
         完了後、tfmarge.exeを閉じて下さい。
プルダウンより指定できる拡張子は限られますが、マージ対象とするファイルフォルダに拡張子は違うけど、 同一フォーマットのデータを入れて、プルダウンで*.*を選択すれば全てのファイルがマージに対応可能となります。

3.ダウンロード

掲載しているソースコードのライセンスは、CC0 (クレジット表示不要、改変可、商用可) とします。自由に利用して頂いてかまいません。 テキストファイルマージツール(tfmarge)は下記からダウンロードして下さい。ソースコードも同梱しています。

ダウンロード

コメント

このブログの人気の投稿

Excelアドインで日本語形態素解析

階層構造JSONファイルの作成

キーボードのキーコードの一覧表