2013/02/21(木)RubyでUTF-8ファイル名&外部コマンド実行

[2017/05/02 追記有り]
Rubyの``やsystemで外部コマンド実行するときに、実行ファイル自体や引数にUTF-8文字(SJISマッピング無し)が渡せないのであれこれ悩んだ問題。

確認はWindows7 64bit & Ruby1.9.3p0(ActiveScriptRuby)

前提。WindowsはファイルシステムがNTFSならUTF-8ファイル名を命名できる。内部コードもUTF-8になっている(はず)。しかし古いAPIを触ったり、INPUT/OUTPUT周りを見るとまだShiftJIS(Windows-31J)が多い。このズレは厄介で、ハマりどころのA代表。特にRubyの場合、UNIX寄りでWindows特有処理のケアはやっぱり甘い…

閑話休題。Windows&UTF-8の扱いが比較的良くなっているRuby1.9系(Dir.globの引数に.encode('utf-8')などでUTF-8文字列を渡すとUTF-8ファイル名が取れる)でも、マジックコメント入れてUTF-8保存しただけだと外部実行が出来ない。
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-

`utf8_filename`
どうにも通らない。SJISマッピング出来るファイルなら、SJISでやればいいだけの話なのだがUTF-8にあってSJISにない文字(U+2661(はーと)とか)が混ざってると動かない。


で、とりあえず色々やってみたのだが、結論から言うと綺麗に書くのは諦めた。

ちゃんと調べるべきなのだが(やってない)、どうもRubyが叩いてるAPI自体が古いんじゃないかという。

unicode - Ruby system() doesn't accept UTF-8? - Stack Overflow

なので、苦肉の策。
バッチファイルを作成して、それを実行する。
# -*- encoding: utf-8 -*-
# -*- coding: utf-8 -*-

open("tmp.bat", "w"){ |fp|
	fp.write("chcp 65001\n")
	fp.write("utf8_filename\n")
}

out = `tmp.bat`
Windowsのバッチ実行ではコードページが合えば、UTF-8ファイル名も通るのでそれを利用する。引数にファイル名を指定するときも同様。\\の数とかダブルクオート囲いとか先頭に./が必要な場合は適切にすること。

うーん、汚すぎて死にそう。

[参考]
Rubyで外部コマンドを実行して結果を受け取る方法あれこれ #Ruby - Qiita

systemuは知らなかった。

[2017/05/02 追記]
やはりきれいには書けていないが、さすがにbatを吐き出すのは汚すぎるということで苦肉の策第二段。小改善してみた。今回はnyagosを使用している。
	out = ""
	Open3.popen3("c:/********/nyagos.exe"){ |stdin, stdout, stderr, thread|
		stdin.puts("chcp 65001")
		stdin.puts("#{COMMAND} \"#{utf8_filename}\"")
		stdin.close
		out = stdout.read
	}
nyagosでなくても良いのだが、cmd.exeやnyaos.exeだとchcp 65001したあとにUTF-8を流し込めない。
ここらへんは、chcp後に貼り付けが出来るかどうかで確認する。

Windows APIを直接叩けば済むような問題に見えるんだよなあ。
ここはまた次回。

2012/06/16(土)AutoIt!でBTScanをちょっと改良

BTScanの記事なのかAutoIt!の記事なのか判断付かないけどとりあえず書いておこう。

AutoIt!はuwsc等で知られる、いわゆるWindows自動化ツール(マクロレコーダ)である。

uwsc(無料版)と比較すると、
[劣る]
・自動記録がちょっと弱い(いわゆる座標依存系のスクリプトを吐くことが多い)
・専用エディタが微妙に使いづらい
(一度拡張子を.au3にしないとメニューが開かないって!)
・仕様に慣れるまでに時間がかかる

[優る]
・とにかく高機能
・正規表現文字列操作もファイル操作もなんでもあり
・GUIも作れる、フォームエディタ付きで作りやすい
・exeもお手軽に作れる(uwscだとPro版のみ)

といったような感じ。どっちかというとマクロいじってると言うよりほぼ一言語を覚えるのに近い。類似のことはやろうと思えば.NET系列なりRubyなりでもできるわけで、初期学習コストが高そうなAutoIt!を使うメリットはかなり微妙なところではある。とはいえ、やっぱりお手軽感はあるか。

さて、今回BTScanにちょっと機能が欲しいってんで、AutoIt!でスクリプトを書いてみた。

目的としては、カウンタリセットボタンが欲しいのと、フォルダ名の最下位部だけ変更するような機能を追加したい。

AutoIt!で書くとこんな感じ。
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#Region ### START Koda GUI section ### Form=
$Form1 = GUICreate("BTScan Tool", 623, 75, 192, 114)
GUISetFont(9, 400, 0, "MS Pゴシック")
$inputFolder = GUICtrlCreateInput("", 8, 8, 489, 20)
$buttonReset = GUICtrlCreateButton("カウンタリセット", 456, 40, 161, 25)
$buttonFolderSet = GUICtrlCreateButton("フォルダ名セット", 504, 8, 113, 25)
GUISetState(@SW_SHOW)
#EndRegion ### END Koda GUI section ###

Run("c:\\hogehoge\\BTScan\\BTScan.exe")
WinWaitActive("[Class:#32770]")

While 1
	$nMsg = GUIGetMsg()
	Switch $nMsg
		Case $GUI_EVENT_CLOSE
			Exit
		Case $buttonFolderSet
			$org = ControlGetText("BTScan 3", "", 1000)
			$newFolder = GUICtrlRead($inputFolder)
			$newPath = StringRegExpReplace($org, "^(.*?)\\([^\\]*)$", "$1\\" & $newFolder)
			ControlSetText("BTScan 3", "", 1000, $newPath)
		Case $buttonReset
			ControlSetText("BTScan 3", "", 1005, "1")
	EndSwitch
WEnd
これを実行すると、ボタン2つテキストボックス1つのフォームがBTScanと共に立ち上がる。hogehogeは適当に読み替えて欲しい。例外処理とかは全く考慮無し。

ちなみに、コメントで囲まれたGUI部分はフォームエディタをWYSIWYGにいじってそのまま貼り付けただけ。

このフォームは別のファイルで保存しておける。コピペの手間が発生するとは言え十分にありレベル。

…うーん、でもやっぱり他の言語使った方が良い?

(2012/7/12) スクリプト微修正。TWAINダイアログ出てても変更反映可に。

2011/12/27(火).NETのSerialPortでケーブルを抜くと例外

.NET(WinXP, .NET 3.5で発生)のSerialPortクラスで、USB-Serial変換ケーブルを使用している際に起こる問題。このケーブルに紐づくCOMポートをOpen()後、Close()前にケーブルを抜くと例外が発生する。

原因は.NET側のバグ。ケーブルの挿抜というかCOMポートが消えることを考慮していない。

発生する例外は把握している限りで2種(UnauthorizedAccessException, ObjectDisposedException)。前者は、Dispose()中に発生する。後者は、SerialPort内部で動いてるThreadが出してくる。

UnauthorizedAccessExceptionはラッパークラスを作って、Dispose()をオーバーライドしtry~catchで囲ってやることで対処可能。以下を参照。簡単なのでコードは書かない。

街角のリブロガー: C# SerialPortのエラー「UnauthorizedAccessException」対策

ObjectDisposedExceptionの方はちょっと難しい。発生箇所は.NET内部の別スレッドのようで、捕まえられる場所を思いつかない(存在しない?)しタイミングも不定。Thread.GetDomain().UnhandledExceptionで発生タイミングはとらえられるが、ここではキャンセルできない。古い.NET、おそらく1.1以下だとこの手の例外は処理継続となるのだが、.NET 2.0以降はアプリが落ちてしまう(WinXP/Win7, .NET 3.5で確認)。

ということで、どうもコードでの容易な対処方法がないようだ。アプリケーション構成ファイルでの回避策があるらしいので以下を参照。

SerialPort Crashes after disconnect of USB COM port | Microsoft Connect

こんな感じでよい。

program.cs
[STAThread]
static void Main()
{
	...

	//メインスレッド以外の非ハンドル例外処理
	Thread.GetDomain().UnhandledException +=
		new UnhandledExceptionEventHandler(Application_UnhandledException);

	...
}

public static void Application_UnhandledException(
	object sender,
	UnhandledExceptionEventArgs e
){
	if( e.ExceptionObject != null &&
		e.ExceptionObject.GetType() == typeof(ObjectDisposedException)) {
		
		//.NET3.5のSerialPortのバグで、ケーブル挿抜後にハンドル不能の
		//ObjectDisposedExceptionが返ることがある。このため、未ハンドル例外処理にて
		//ObjectDisposedExceptionについてのみ無視する。

		//何もしない
	} else {
		//何かメッセージを出力
		
		Application.Exit();
	}
}
以下のアプリケーション構成ファイルをアプリケーションと同じディレクトリに置く(hoge.exeが実行ファイルなら、hoge.exe.configとなる)。

hoge.exe.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<runtime>
		<legacyUnhandledExceptionPolicy enabled="1"/>
	</runtime>
</configuration>
構成ファイルを上記のように書くと、非ハンドル例外に関しては処理継続となる。釈然としないが、どうもこれが楽な方法らしい。

その他の非ハンドル例外を無視して良ければ、program.cs側の記述は不要。

.NETのバージョン変更、OSの変更で直る可能性有り。

2008/04/20(日)音量アイコンが消える

再起動、復帰時などにタスクトレイ上の音量アイコンが消える問題。

発生状況等まとめると、

1.[コントロールパネル->サウンドとオーディオデバイス->タスクバーに音量アイコンを配置する]にチェックは入っている。

2.にもかかわらず起動時に表示されないことが多い。

3.チェックを入れ直して適用すると表示される。

4.音量アイコン以外にも、「ハードウェアの安全な取り外し」や「Daemon Tools」のアイコンが表示されていない。
という状況。

さて、毎度のことながら原因ははっきりとはつかめなかったが対処方法は発見した。

http://hotstreet.vaio.sony.co.jp/article/article.php?id=6166
ここにあるとおり、どうもUPnPまわりのタスクトレイ表示で何か阻害が起こるらしい。

従って対処方法としては、

1.[マイネットワーク->ネットワークタスク->ネットワークに接続しているUPnPデバイスのアイコンを非表示する]をクリック

2.再起動
で終了。これで全部表示されるように改善された。やれやれ

ネットワークタスクが表示されていなければ、[ツール->フォルダオプション->フォルダに共通の作業を表示する]にチェックを入れ、フォルダの表示を切って、サイドバーにネットワークタスクが表示されるようにする。

なお、すでに[マイネットワーク->ネットワークタスク->ネットワークに接続しているUPnPデバイスのアイコンを表示する]となっていれば、この対処法は外れ。ごめんなさい。

起動時に重いタスクが走ってるとアイコン登録に失敗するという情報もキャッチしたのだが確認できなかった。ちゃんとログオンのステップを踏んでいる人はこの問題は発生しないかもしれない。

2008/02/13(水)BDS2006(Turbo C++)でBoostをコンパイルするときのあれこれ

先に結論から。通ったときのコマンド列と環境を書いておこう。

[環境]

BDS2006(Turbo C++) - bcc32(5.8.2)

Boost 1.34.1

boost-jam 3.1.16-1-ntx86



bjam.exe -sTOOLS=borland --toolset=borland --prefix="C:\Program Files\Borland\BDS\4.0" install



(cmd.exe上から)

さてこれは何度か失敗した上で結果的に成功したときの環境をメモしたもの。

失敗したときのことを思い返すに、何がダメだったのかよく分かっていないのだが、可能性がありそうなものを以下に全部列挙し次回の参考にしたいと思う次第。

・-sTOOLS=borlandだけだとborlandが無いといわれ、msvcでコンパイルされる(失敗)。



warning: No toolsets are configured.

warning: Configuring default toolset "msvc".

warning: If the default is wrong, you may not be able to build C++ programs.



・--toolset=borlandは必要なのかそれだけでいいのかよく分かっていないが、上記の失敗改善には有効。

・nyacusからだとダメかもしれない。

・Boost側がコンパイルさせるBCCのバージョンに対応していれば、BCBBoostは不要(おそらく通るライブラリは増えないし、コンパイルに失敗することがある)。

・今回のBoost-1.34.1はBCC32(5.8.2)には対応していた。(調査不足)

結局、

1.自分の使っているBCCのバージョンを調べ

2.Boostが対応しているか確認し

3.対応していなければBCBBoostを当て

4.--toolsetをつけてコンパイル
ということなのだろうか。しっくりこないなあ。

BCBBoostを当てたときは-STOOLの指定を自分のコンパイラに合わせたものにする。ここら辺はその都度BCBBoostのドキュメントやForumをあさること。

さて、Boostの対応はVC++のほうがしっかりしているのは周知の事実だ。Boostの機能をフルに使いたいならば、BCBは良い選択ではない。regexはいけそうだが、相変わらずtype_traits周りがダメくさい。

対応状況はBoostのtest項目などを調べればわかる。pythonは全滅しているようだが、python環境のインストールによってはいけるのかどうか?

[参考サイト]

http://www.kmonos.net/alang/boost/build.html

http://www.gesource.jp/weblog/archives/2006/12/borland_developer_studio_2006_1.html

http://d.hatena.ne.jp/ex_2/searchdiary?word=Boost

http://www.gesource.jp/programming/bcb/46.html

2008/02/12(火)BDS2006(Turbo C++)でSTLportをコンパイルするときのあれこれ

[環境]

BDS2006(Turbo C++)

STLport5.1.5

minGW5.1.3
STLportのインストールで軽くはまったのでメモ。

STLportのドキュメント通りに作業したのだが、どうもパスがらみでmakeが上手く通らない。色々やってみたのだが、%BDS_DIR%\Binの.cfgを見にいっていないようである。プロンプトやシステム環境変数で指定してみたのだが、やっぱりダメ。むむ手強い。

一応の解決方法だが、まずSTLport内のREADME.borlandの指定通りに.cfgを書き換え(今回は必要なかった)、コマンドライン上でset INCLUDE=%BDS_DIR%\INCLUDE をしておく。しかしこれでも通らないようなので、.cfgをビルド作業ディレクトリ(%STLport_DIR%\Build\Lib)にコピーし、bcc.mak内の



STLPORT_INCLUDE_DIR = ../../stlport







STLPORT_INCLUDE_DIR = "../../stlport";"C:\Program Files\Borland\BDS\4.0\include"



に書き換える。

で、


mingw32-make.exe -fbcc.mak install

やっとコンパイル完了。

うーん、なにか根本的なところで理解が足りてない気がするなあ。ただ、やっぱりBCC32あたりの検索パスは何か変ではある。

http://members.jcom.home.ne.jp/komina/wiki/424453323030362F53544C706F7274A4F2BBC8A4A4A4BFA4A4.html
でやってるように、エラーが出たら-I/-Lオプション足して打ち込み直す方がシンプルな解答である気もする。

2008/02/12(火)BCC32周りの仕様

C++ BuilderやBCCを使っているときにも感じたのだが、どうもBorlandのC++関連Bin群の振る舞いは妙に感じるときがある。

C++ Compilerとしての性能云々以前に、コマンドラインから扱う上でライブラリ検索の仕様が妙に整合性がない。Borland(Inprise)の中の人がこんなことに気づいてないはずはないのだが、一度出してしまってる以上影響が大きすぎて変えようがないのだろう。IDEとかどっかで吸収してくれということのようだ。

ライブラリやインクルードパスについて、まずBCC32,BRCC32,ilink32のいずれでも、PATH変数で汚く指定する方法では検索しにいってくれない。コマンドラインオプションで明示するか、.cfgに書く必要がある。bcc32,ilink32はこれでよいが、BRCC用の.cfgは無いようだ。

.cfgに書くときであるが、どうも.cfgをBinの中において、Binにパスを通しても見てくれないときがある。確実なのは、コンパイル作業するときの作業ディレクトリにコピーしてやること。これなら確実に見てくれるようだ。

ただこれらは、minGWからの中からの話なので、実のところ何かPATH継承外のシェルをforkしてるだけかもしれない。ということで、ここらへんはあくまでそうかもしれないという話だけで、次記事のSTLportコンパイルの時に具体的な話を書くことにする。
OK キャンセル 確認 その他