Windowsでもgrepしたい!findstr コマンドをもっと使おう!

やあ子供たち。今日はちょっとCEDECの講演タイトルばりのキャッチ―なタイトルにしてみたよ。
Windowsのフォルダの中に、テキストファイルがたくさんあって、(そう、例えば何かのアプリが出力したログだったりね)、でその中に特定の文字列を含むものがあれば、該当するファイルのファイル名と、出現行を、あますところなく教えてほしい。


そんな時は、LinuxなどのUnix系システムではgrepコマンドを当たり前のように使うわけなのであるが、じゃあWindowsでは?ということになってくると、おじさんは今までエクスプローラの右上の方にある検索窓に何かを打ち込むようなことしか思いつかなかったんだけれども、この検索窓は何やら気まぐれで、うまく働いてくれる時とそうでない時とがあったりする印象だったんだよな。


そこで今日は「windows grep equivalent」という検索ワードでググってみたよ。するとどうだ、なんとなんと
Windowsにはfindstrというコマンドプロンプト上で使えるコマンドがあって、この日記もおじさん自身の備忘録メモという位置づけなのでここにメモしておこうかなということ。
さて使用方法は、コマンドプロンプトを立ち上げて以下を実行するとよくわかりますよと。

findstr /?|more

そしてその出力は以下のようだ。使用方法だね。

ファイルから文字列を検索します。

FINDSTR [/B] [/E] [/L] [/R] [/S] [/I] [/X] [/V] [/N] [/M] [/O] [/P]
        [/F:ファイル] [/C:文字列] [/G:ファイル] [/D:ディレクトリ一覧]
        [/A:色属性] [/OFF[LINE]] 文字列 [[ドライブ:][パス]ファイル名[ ...]]

  /B           行の先頭にあるパターンを検索します。
  /E           行の末尾にあるパターンを検索します。
  /L           検索文字列をリテラルとして使用します。
  /R           検索文字列を正規表現として使用します。
  /S           現在のディレクトリとすべてのサブディレクトリから一致する
               ファイルを検索します。
  /I           検索するときに大文字と小文字を区別しません。
  /X           完全に一致する行を出力します。
  /V           一致しない行のみを出力します。
  /N           一致する各行の前に行番号を出力します。
  /M           ファイルに一致する行があるときに、ファイル名のみを出力します。
  /O           一致する各行の前に文字オフセットを出力します。
  /P           印刷不可能な文字を含むファイルをスキップします。
  /OFF[LINE]   オフライン属性が設定されたファイルをスキップしません。
  /A:属性      2 桁の 16 進数で色属性を指定します。"color /?" を参照してくだ
               さい。
  /F:ファイル  指定したファイルからファイル一覧を読み取ります (/ を指定する
               とコンソール)。
 /C:文字列    指定された文字列をリテラル検索文字列として使用します。
  /G:ファイル  指定されたファイルから検索文字列を取得します (/ を指定する
               とコンソール)。
  /D:ディレクトリ
               セミコロンで区切られた検索されるディレクトリ文字列テキストの
               一覧を検索します。
  [ドライブ:][パス]ファイル名
               検索するファイルを指定します。
複数の文字列を検索する場合には、引数 /C を使わず、各文字列をスペースで区切り
ます。
たとえば、FINDSTR "hello there" x.y と指定した場合は、ファイル x.y で "hello"
または "there" が検索されます。
これに対して、FINDSTR /C:"hello there" x.y と指定した場合はファイル x.y で
"hello there" が検索されます。

正規表現クイック リファレンス:
  .            ワイルドカード: 任意の文字
  *            繰り返し: ゼロ個以上の直前の文字またはクラス
  ^            行位置: 行頭
  $            行位置: 行末
  [class]      文字クラス: セットの任意の 1 文字
  [^class]     逆クラス: セット以外の任意の 1 文字
  [x-y]        範囲: 指定した範囲の任意の文字
 \x           エスケープ: メタ文字 x のリテラル使用
  \<xyz        単語位置: 単語の先頭
  xyz\>        単語位置: 単語の終わり

FINDSTR の詳細な正規表現に関しては、オンライン ヘルプのコマンド リファレンスを
参照してください。

どうだいこれは。とくにサブフォルダ構造を階層的にたどってくれる/Sオプションなっていう便利そうなものもあるじゃないですかー。
そして例えばの使用方法はこう。

findstr /R /C:"Taro" .\*.csv

これはカレントフォルダの中にある、あまねく.csvファイルに対し、その中に"Taro"という文字を含む.csvファイルをすべて、その出現行とともに出力してくれるよ。

どうだこれ一行でだぞ。Windowsなんかだと何かそういうフリーソフトとか探しに行っちゃいそうだけどそんなもの一切必要ないからねもう。このfindstrはいやいや色々オプションがたくさんあって使えそうな気がするなー。
じゃあ今日はこんなところだ。チャオ!

レシート打ち込みはGoogleドキュメント音声入力とGoogleSpreadSheetにお任せ☆彡

やあ子供たち元気でやっているか。夏休みで慣れないレンタカーで行かなきゃならないからって、怖気づいていたりしやしないかい。今日はちょっとした小技を覚えたので紹介するぞ。

おじさんは月に一回財布の中にたまったレシートをSpreadSheetに打ち込むということをもう何年もやっているのだけど、これがまあ面倒だったんだよ。テンキーを導入した方が少しは楽になるだろうかとか、あまり助けにはならなさそうな発想しかこれについては出せずに結局毎月手動打ち込みをし続けてきてしまった。
しかし、「サボろうとすることは技術進化や作業効率化の父」とよく言われるように(この語句通りではないにしても同じ意味のことがよく言われているよね)、もうめんどくさいから音声入力とかでせめてできないものかとちょっと調べてみた。

で、ここがポイントなんだが、Windowsとか、まPC環境なんでね、Windowsとかで何かツールを、、っていう切り口で調べてもそれは何も良い結果がヒットしません。有料のものだったり、OSの機能としてもあるようだが何だかわかりにくかったり動かなかったりね。動かねーじゃねーか>ディクテーション!現在の言語では対応してません?おいおい今どんな時代だよ!?

ところが、答えはすべてブラウザの中にあった。そう、Chromeブラウザ+Googleドキュメントね。Googeドキュメントのツール>音声入力とやると、とても性能のいい音声解釈エンジン(というか最近はSiriとかGoogleHomeとかですっかり慣れているけどね)が、そのままマイクに喋った内容をテキスト化してくれるんだ。

まずは日付。
日付は、例えば「〇月□日」だったら、そのまま話しかけてはだめさ。のちにSpreadSheetで、DateValue()関数でちゃんとした日付型にするために、「〇、スラッシュ、□」と話しかけよう。そうすれば「〇/□」というテキストができて、これをそのままDateValue()関数にながしこめば、現在の西暦年の入ったちゃんとした日付型に変換できるぞ。

次にキーボードからタブを打とう。
スペースでもいいが。これはのちにSpreadSheetで、テキストを列に分割するために必要だ。

次に、お店の名前を話しかけよう。これはそのままテキストになる。

次にキーボードからまたタブを打とう。スペースでもいいが。これはのちにSpreadSheetで、テキストを列に分割するために必要だ。

次に、金額を
話しかけよう。でも数字だけでいいぞ「~円」とか言ってしまうと文字列になってしまうからな。

これでレシート1枚分の行が完成したので、キーボードでリターンキーを押して改行し、また次の行に2枚目のレシート情報を上記の容量で入力していこう。

N枚分のレシートを音声入力できたら、N行のGoogleドキュメントテキストができているはずだ。
それをまるごとコピーして、GoogleSpreadSheetに貼付けよう。(とまあここからは話もうExcelでも何でもよいのだがな)
そして、データ>テキストを列に分解を実行することで、
あっ!というまに、「日付、お店の名前、支払った金額」という、3列のスプレッドシートデータが完成だ。

以上!じゃ今日のお話はこれでおしまいだよ。
チャオ!

TimeNeoを作ったよ

やあ子供たち。
time_tのオレオレラッパーを作ってみたので紹介するぞ。最後に簡単なカレンダーの作り方もあるよ。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <iomanip>
#include <ctime>
#include <string>



using namespace std;


#define TIMETNEO_LINEMAX 80
#define TIMETNEO_FORMAT_DATE "%d/%02d/%02d"
#define TIMETNEO_FORMAT_TIME "%02d:%02d:%02d"
class TimeNeo
{
public:
	TimeNeo(void) {
		this->LoadNow();
	}
	TimeNeo(const TimeNeo& t)
	{
		_time = t._time;
	}
	void LoadNow(void)
	{
		_time = time(nullptr);
	}
	string getDateAsText(void)
	{
		int y, m, d;
		this->getDate(&y, &m, &d);
		char ss[TIMETNEO_LINEMAX];
		sprintf(ss, TIMETNEO_FORMAT_DATE, y, m, d);
		return string(ss);
	}
	string getDateTimeAsText(void)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		char ss[TIMETNEO_LINEMAX];
		sprintf(ss, TIMETNEO_FORMAT_DATE " " TIMETNEO_FORMAT_TIME, y, m, d, h, min, s);
		return string(ss);
	}
	void getDate(int* pyear, int* pmonth, int* pdate)
	{
		int h, m, s;// dummies
		getDateTime(pyear, pmonth, pdate, &h, &m, &s);
		return;
	}
	void getDateTime(int* pyear, int* pmonth, int* pdate, int* phour, int* pmin, int* psec)
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		*pyear = tm0.tm_year + 1900;// 年次は1900年からの数え年!
		*pmonth = tm0.tm_mon + 1;   // 月は0から11!
		*pdate = tm0.tm_mday;
		*phour = tm0.tm_hour;
		*pmin = tm0.tm_min;
		*psec = tm0.tm_sec;
	}
	int getYear()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_year + 1900;// 年次は1900年からの数え年!
	}
	int getMonth()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_mon + 1;// 月は0から11!
	}
	int getDay()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_mday;
	}
	int getWeekDay()
	{
		tm tm0;
		localtime_s(&tm0, &_time);
		return tm0.tm_wday;
	}
	void setDate(const int year, const int month, const int date)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_year = year - 1900;// 年次は1900年からの数え年!
		tm0.tm_mon = month - 1;// 月は0から11!
		tm0.tm_mday = date;
		_time = mktime(&tm0);
		return;
	}
	void setDate(const int date)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_mday = date;
		_time = mktime(&tm0);
		return;
	}
	void setDateTime(const int year, const int month, const int date, const int hour, const int min, const int sec)
	{
		// 人間が読める形式に変換
		tm tm0;
		localtime_s(&tm0, &_time);
		// 必要な内容のみ更新
		tm0.tm_year = year - 1900;// 年次は1900年からの数え年!
		tm0.tm_mon = month - 1;// 月は0から11!
		tm0.tm_mday = date;
		tm0.tm_hour = hour;
		tm0.tm_min = min;
		tm0.tm_sec = sec;
		_time = mktime(&tm0);
		return;
	}
	friend double operator-(const TimeNeo& a, const TimeNeo& b)
	{
		return difftime(a._time, b._time);
	}
	time_t operator=(const TimeNeo& a)
	{
		_time = a._time;
		return _time;
	}
	bool operator<(const TimeNeo& a)
	{
		return (a - *this) >= 0;
	}
	TimeNeo& advanceDate(const int dday)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		d += dday;
		this->setDateTime(y, m, d, h, min, s);
		return *this;
	}
	TimeNeo& advanceMonth(const int dmon)
	{
		int y, m, d, h, min, s;
		this->getDateTime(&y, &m, &d, &h, &min, &s);
		m += dmon;
		this->setDateTime(y, m, d, h, min, s);
		return *this;
	}
	TimeNeo& incDate(void)
	{
		return advanceDate(1);
	}
	TimeNeo& decDate(void)
	{
		return advanceDate(-1);
	}
	TimeNeo& incMonth(void)
	{
		return advanceMonth(1);
	}
	TimeNeo& decMonth(void)
	{
		return advanceMonth(-1);
	}
	time_t _time;
};

int main(int argc, char* argv[])
{
	TimeNeo tn0;
	TimeNeo tn1;
	tn1.setDate(1971, 2, 17);
	cout << tn0.getDateTimeAsText() << endl;
	cout << tn1.getDateTimeAsText() << endl;
	{
		int y, m, d, h, min, s;
		tn0.getDateTime(&y, &m, &d, &h, &min, &s);
		d += 30;
		h += 12;
		tn0.setDateTime(y, m, d, h, min, s);
		cout << tn0.getDateTimeAsText() << endl;
	}
	TimeNeo tn2 = tn0;
	{
		int y, m, d, h, min, s;
		tn2.getDateTime(&y, &m, &d, &h, &min, &s);
		s += 30;
		tn2.setDateTime(y, m, d, h, min, s);
		cout << tn2.getDateTimeAsText() << endl;
		cout << "difftime: " << tn2 - tn1 << endl;
	}
	TimeNeo tn3;
	{
		int y, m, d, h, min, s;
		tn3.getDateTime(&y, &m, &d, &h, &min, &s);
		s-=1.6e9;
		tn3.setDateTime(y, m, d, h, min, s);
		cout << tn3.getDateTimeAsText() << endl;
	}
	cout<< ( tn3 < tn0) <<endl;

	/////////////////////////////////
	// TimeNeoをつかってカレンダーを作ろう。
	//
	TimeNeo idate;
	idate.setDate(2022, 8, 17);// ターゲット月の設定
	cout << "**** Calendar "
		<< setw(4) << to_string(idate.getYear()) 
		<<"/"<< setw(2) << setfill('0') << to_string(idate.getMonth()) 
		<<" ****" << endl;
	cout << "Sun Mon Tue Wed Thr Fri Sat" << endl;
	{
		// ターゲット月の左上の日付を得よう。
		idate.setDate(1);
		while (idate.getWeekDay() != 0)
			idate.decDate();
		// ターゲット月の右下の日付の次の日を得よう。
		TimeNeo edate = idate;
		{
			edate.incMonth();
			while (edate.getWeekDay() != 0)
				edate.incDate();
			edate.decDate();
			//cout << edate.getDateAsText() << endl;;
		}
		// ターゲット月を打ち出そう
		//cout << idate.getDateAsText() << endl;
		int col = 0;
		while (idate < edate)
		{
			cout << setw(2) << to_string(idate.getDay()) << "  ";
			if (++col == 7)
			{
				cout << endl;
				col = 0;
			}
			idate.incDate();
		}//while
	}
	return 0;
}

localtime_sでええやないか

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <time.h>
#include <vector>
#include <algorithm>
#include <map>

using namespace std;



int main(int argc, char* argv[])
{
	time_t now = time(nullptr);
#if 0
	tm nowtm = *localtime(&now);
#else
	tm nowtm;
	localtime_s(&nowtm, &now);
#endif
	cout << nowtm.tm_year+1900 << "/" << nowtm.tm_mon << "/" << nowtm.tm_mday << endl;
	cout << nowtm.tm_hour << ":" << nowtm.tm_min << ":" << nowtm.tm_sec << endl;
	return 0;

新しいExcelではutf-8なCSVのエクスポートができるぞい

やあ子供たち。元気でやっているか。最近おじさんは熱帯魚飼育を始めてみたよ。水が白く濁ってなかなか治らなくてな。まあ何でも勉強で、大変なんだけれども。
さて今日はExcelutf-8CSVをエクスポートしたいという内容だよ。(※本記事は、最近までおじさんが知らなかっただけの話だろという側面もありますので見下ろす感じで読んでくれても大丈夫です。)

●旧来からの「CSVとして出力」だとShitftJISになってしまう。
いわゆる環境依存文字、例えば日本語圏以外のマルチバイト文字を含むExcelシートをCSVとしてエクスポートしたい場面は昨今よくあるわけだが、過去これまでのExcelで俺たち日本人なら誰もがやってきた「名前をつけて保存」でCSVとして出力をするやり方だと、文字コードはShiftJIS固定で出力されてしまうので、Excelの中の世界ではちゃんと表示されていたそうした外国系の文字は、CSVに出力した途端に?などがついて、なんとも悲しい文字化けCSVとしての出力しかできなかった。

「日本語をShiftJISのCSVで出力してメモ帳でutf-8にすればええやん」そうじゃない。そういう話をしているのではありません。普通の標準の日本の漢字しか含まれていないExcelシートだったら、もちろんそれでOKで、誰も困らないよ。メモ帳でutf-8化しちゃってくださいそれで話は終わりだよ。
そういうケースではなくて今は、日本語以外の、外国の見たことないような環境文字(或いは日本語でも標準の漢字ではない難しい漢字などが含まれているExcelファイル)の話をしているよ。そういうExcelファイルをCSVに出力する場合の話をしているよ。本来Excelの中でちゃんと表示されていたこれら普段は見ないような環境文字がですね、旧来のExcelCSVエクスポートすると、ShiftJISになって「潰れて」しまうからこまるよねと、そういう話をしているんだよ坊やわかるかい。

これがために以下のような解決策を右往左往する必要があった。

  1. UniCodeテキストとして出力 UniCode?。「名前をつけて保存」でUniCode、つまりUTF16テキストとして出力する方法。これはカンマじゃなくてタブ区切りのUTF16テキストとして出力してくれるので、メモ帳などでタブをカンマに置換してutf-8として保存しなおす方法。文字はちゃんと変換されてくれて有難いのだけれども。。けれども罠があって連続する空のカラムがあると区切ってくれるはずのタブを全部まとめて1個のタブとして出力してくれるから列情報がめちゃめちゃになるので、事実上、空のカラムが1個もないとわかってるシートでしか使えなさそう。
  1. Excelのブック内容を読みに行って文字コードutf-8でテキスト出力するためのVBAやC#を、力ずくで書く。(これが最終手段か?)

●新しいExcelでは、「CSVとして出力(ただしutf-8でな)」が選べる!
はいここからが本題だ。ところがところが上述の心配や努力は一切必要なくなる新機能の登場だ。新しいExcelでは、「名前をつけて保存」の中に、もう普通に「CSVとして出力(ただしutf-8でな)」が、何も考えずに誰でも簡単に!これをやればいいだけなんですみなさん!今日は特別価格で、1万円を切りました!さらにさらに、今ならお得な1年分パックもつけちゃいます!(←最後のはExcelの価格とかじゃないからな。ふざけてるだけだよ。)

はい。みんなも最新のExcelを買って時間や人手をセーブすることを検討してみてはいかがだろうか。
※ていうか、Excelもそんなに最新じゃなくてって話もある。Excel2016あたりから使おうと思えば使えるオプションだったという噂も。最近までおじさんが知らなかっただけの話っていうのは本当にそうなので。
今日のネタは以上でおしまいだよ。チャオ!

HTTP range リクエストも、express.static()にお任せ

やあ子供たち。express.static()は中で、動画はちゃんとストリーミングで配ってくれてるのだろうとかいったあたりの中身に挙動がよくわからなかったので、動画ファイルのリクエスト来た時だけは自前のミドルウェアで捕まえて、単純な自前実装のストリーミング配信実装通すようにしてたら、とあるクライアント環境ではrangeリクエストに対応してないから動画見れないっていうケースのあることに気づいたが、ふと思いついてexpress.static()に任せきりなコードに戻した(つまり自前ミドルウェアを全コメントアウト)ら、その動画見れなかった環境でも問題なく動画見れちゃって、あれ、結局express.static()に任せきりでよかったんだって今日なった話を忘れずにメモしておきたかった。
express.static()の前身は、serv-staticというライブラリだったらしく、serv-staticでは、rangeリクエストへの対応がデフォルトでONになっているみたいな記事を読んでもしやと思ってやってみたらやっぱりそういうことだったのかみたいな。