WSHでファイル操作など

# 前置き

長いログファイルを元に大量のCSVのデータチェックを行う必要があり、手作業ではあまりに辛い。やる気が微塵も起きない。自動化が急務である。
データを扱いやすいよう対象のCSVをDB取込などするのもありだが、何十本ものCSVを取り込むだけでも手間だ。嫌だ、働きたくないでござる。

真面目にコツコツ成果物を出せる人間は称賛されるべきだ。それに異論を述べるつもりはないし、僕も心から尊敬する。だが労働意欲のない不真面目な僕にだって、称賛されたい気持ちだけは人並みにある。ついでに言うと、コンパイルはしたくないし読み書きやすい文法でどこでも使えるやつでお願いします。

やれやれ、僕は検索した。


# WSHをやってみる

[WSH JScriptを使いこなそう](https://jscript.zouri.jp/)

`WSH(Windows Script Host)`を用いることで、Windows環境下であればたいていデフォルトで動くスクリプトが作れるようだ。しかもVBだけでなくJScript（ほぼほぼJavascript）で書ける。もちろんコンパイル不要だ。最高だ。**Write once, run anywhere**の精神である。

書き方はおおむね、みんな大好きJavascriptでよい。
注意点として、最新のJavascriptと比べて多少差異がある。たとえば変数の型は全て`var`で、`let`や`const`はエラーとなる。

```hoge.js
var hoge = "hoge";  // これはOK
const hogege = "hogege";  // これはNG
```

文字列中の変数展開もNGだ。

```hoge.js
`こんにちは！ ${hoge} さん`
```

ささいな問題である。

また、Windows上で動作するので、日本語を扱うならばスクリプトは`SJIS`で書く必要がある。極東島国のWindowsユーザにとって`UTF-8`は不要なエンコーディング方式であるから、やはりささいな問題である。GHQの偉い人もそう言うてはる。

UTF-8で書いた場合

```hoge.js
WScript.Echo("おひゃらひゃら");
```

```console.log
Microsoft (R) Windows Script Host Version 5.812
Copyright (C) Microsoft Corporation. All rights reserved.

縺翫・繧・ｉ縺ｲ繧・ｉ
```
まあ化ける。

# テキストファイル操作

通常のテキストファイルを操作したい場合は、ファイルオブジェクトを作成して読み書きすればよい。簡単だ。熟練のエンジニアなら目をつぶってても書けるだろう。僕は熟練ではないのでどっかからコピペしました。

1行読み込んで表示してみる

```hoge.js
var fs = new ActiveXObject("Scripting.FileSystemObject");
var file = fs.OpenTextFile(filePath, 1, true, 0);
while(!file.AtEndOfStream) {
  WScript.Echo(file.ReadLine());
}
file.Close();
fs = null;
```

末尾に書き込んでみる

```hoge.js
var fs = new ActiveXObject("Scripting.FileSystemObject");
var file = fs.OpenTextFile(filePath, 8, true, 0);
file.WriteLine("あいうえお");
file.Close();
fs = null;
```

# CSV操作

CSVファイルをDB的にSQL検索することも可能だ。はじめは単にCSVを効率よく読み込みたいだけのつもりだったが、調べた結果予想以上に手軽であり、今回一番感動したところである。

[WSHサンプル集](https://wiki.bit-hive.com/y10/pg/WSH%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E9%9B%86)

```hoge.js
var con = new ActiveXObject("ADODB.Connection");
con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\hoge\\csvDir;Extended Properties=\"text;HDR=NO;FMT=Delimited;\";";
con.Open();
var rs = con.Execute("SELECT * FROM `hoge#csv` ORDER BY F1");
if (rs) {
  while (!rs.EOF) {
    WScript.Echo(rs.Fields("F1").Value);
    rs.MoveNext();
  }
}
rs.Close();
con.Close();
```

上記コードでいうと、`HDR=NO`でヘッダなしとして読み込んでいる。するとカラム名は**F1**,**F2**...と自動的に認識される。ファイル中にヘッダ行を含んでいる場合はデータと混じってしまうが、そのあたりは使いみちに合わせて検討して頂きたい。
一行目の値をカラム名とする場合は`HDR=YES`とすればよろし。F1,F2形式はできなくなるので注意。

# 実行

実行に際しては作成したファイルをコマンドプロンプトから呼び出してもいいのだが、堕落した民がそんなことをする道理がない。道理のないところに処理は動かぬとは孔子の言であるが、現代にも通ずる。したがって`.bat`ファイルを作れば面倒さを憂う前に実行できる。**Write once, run anytime**の精神である。

64bitならこのような呼び出しをおすすめする。最後のjsファイル名だけ変えてあげればよい。

```hoge.bat
C:\Windows\SysWOW64\cscript /E:jscript ./hoge.js
pause
```

なぜかというと、上述のCSV操作をするにあたって用いるADODBは、これでないと下記エラーを吐く。

```console.log
ADODB.Connection: プロバイダーが見つかりません。正しくインストールされていない可能性があります。
```

CSV操作とか特殊なことしないのであれば、これでも動く。

```hoge.bat
CScript /E:jscript ./hoge.js
pause
```

# 実行引数

実行引数を取りたい場合はこのあたりが参考になるかと思う。やってない。
https://daybydaypg.com/2017/12/22/post-868/
https://ooo.iiyudana.net/htm/js_chp3frame.htm


# サンプル

テスト用にこういったディレクトリ構成を作成してみる。

```
C:\hoge\
   ├─ hoge.bat
   ├─ hoge.js
   ├─ csvDir\hoge.csv
   └─ textDir\
```

あとはテキストエディタでそれぞれコピペしてみてください。

**注意：csvファイルのエンコードは`SJIS`で。**

```hoge.csv
"ID","NAME","TEXT"
"1","AAABBBCCC","あべべのべ"
"2","BBBCCCDDD","なのなのな"
"3","CCCDDDEEE","おひょひょのひょ"
```

```hoge.bat
C:\Windows\SysWOW64\cscript /E:jscript ./hoge.js
pause
```

**注意：jsファイルのエンコードは`SJIS`で。**

```hoge.js
// オープンモード
var FORREADING      = 1;    // 読み取り専用
var FORWRITING      = 2;    // 書き込み専用
var FORAPPENDING    = 8;    // 追加書き込み

// 開くファイルの形式
var TRISTATE_TRUE       = -1;   // Unicode
var TRISTATE_FALSE      =  0;   // ASCII
var TRISTATE_USEDEFAULT = -2;   // システムデフォルト

var CSV_DIR = "C:\\hoge\\csvDir";
var TEXT_DIR = "C:\\hoge\\textDir";

main();

function main() {
  // 2列目に「AAA」を含むデータを検索する
  checkCSV(CSV_DIR, "hoge.csv", "AAA");

  // テキストファイル内容をチェックする
  checkText(TEXT_DIR);
}

function checkCSV(dir, fileName, keyword) {
  // ファイル名の.を#に置換する
  fileName = fileName.split('.').join('#');

  var con = new ActiveXObject("ADODB.Connection");
  con.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + dir + ";Extended Properties=\"text;HDR=NO;FMT=Delimited;\";";
  con.Open();
  var rs = con.Execute("SELECT * FROM `" + fileName + "` WHERE F2 LIKE '%" + keyword + "%' ORDER BY F1");
  if (rs) {
    while (!rs.EOF) {
      var tmpMsg = [rs.Fields("F1").Value,
                    rs.Fields("F2").Value,
                    rs.Fields("F3").Value];

      WScript.Echo(tmpMsg);
      writeToTextFile(TEXT_DIR, "hoge.txt", tmpMsg[2]);
      rs.MoveNext();
    }
  }
  rs.Close();
  con.Close();
}

function writeToTextFile(dir, fileName, value) {
  // ファイルを追記で開く
  var fs = new ActiveXObject("Scripting.FileSystemObject");
  var file = fs.OpenTextFile(dir + "\\" + fileName, FORAPPENDING, true, TRISTATE_FALSE);
  // テキストに書き出し
  file.WriteLine(value);
  // ファイルを閉じる
  file.Close();
  fs = null;
}

function checkText(textDirPath) {
  // ファイル操作オブジェクト
  var fso = new ActiveXObject("Scripting.FileSystemObject");
  // 対象ディレクトリの全ファイル取得
  var files = fso.GetFolder(textDirPath).Files;

  // 1ファイルずつ読込
  var e = new Enumerator(files);
  for (;!e.atEnd();e.moveNext()) {
    var file = e.item();
    // 読む
    readFromTextFile(file.Path);
  }
}

function readFromTextFile(filePath) {
  // ファイルを読み取り専用で開く
  var fs = new ActiveXObject("Scripting.FileSystemObject");
  var file = fs.OpenTextFile(filePath, FORREADING, true, TRISTATE_FALSE);

  while(!file.AtEndOfStream) {
    // 1行読み込んで表示
    WScript.Echo(file.ReadLine());
  }
  // ファイルを閉じる
  file.Close();
  fs = null;
}
```

# 実行結果

hoge.batをダブルクリックすると、コンソールにはこのように出るはずです。

```console.log
1,AAABBBCCC,あべべのべ
あべべのべ
```

textDir/hoge.txtを開くと「あべべのべ」と書き出され、バッチを叩くたびに「あべべのべ」が増殖する。**Run once, write once**の精神である。