作成者別アーカイブ: skmk

CaboChaの文字コードRecompileしてもうまくいかない、そんなとき

CaboCha 0.60pre4 Windows版で辞書の文字コードを変えようと思ったのだけどうまくいかなかったときの話 CaboChaをインストールすると、スタートメニューには

  • Recompile SHIFT-JIS Model
  • Recompile UTF-8 Dictionary というのが用意されているのだけれど、これをこのまま実行してもCaboChaがうまく動作しないことがある。

症状

たとえば、文字コードをUTF-8からSHIFT-JISに変えようと思って「Recompile SHIFT-JIS Model」を実行。特にエラーもなく終了したので、CaboChaを実行すると、

morph.cpp(108) [charset() == decode_charset(dinfo->charset)] Incompatible charse
t: MeCab charset is SHIFT-JIS, Your charset is UTF8

となり係り受け解析ができない。

原因

Windows Vista以降で導入されたユーザーアカウント制御UAC(User Account Control)により、C:\Program Files\CaboCha\model\charset-file.txtの書き換えに失敗している。

C:\Program Files\CaboCha\model>echo SHIFT-JIS 1>charset-file.txt
アクセスが拒否されました。

対策

Recompileのバッチ実行後に直接C:\Program Files\CaboCha\model\charset-file.txtのファイルを書き換える。 もしくは、 C:\Program Files\CaboCha\model\mkmodel.batの1行目に以下を追加して、”管理者権限で”Recompileのバッチを実行する。

 cd /d %~dp0 

なぜだろう

UACにより書き込みが制限されるのであれば、最初からバッチコマンド自体を管理者権限で実行すればよいのでは? しかし次のようになってしまう。

C:\Windows\system32>..\bin\cabocha-model-index -f SHIFT-JIS -t SHIFT-JIS dep.ipa
.txt dep.ipa.model
指定されたパスが見つかりません。

C:\Windows\system32>..\bin\cabocha-model-index -f SHIFT-JIS -t SHIFT-JIS chunk.i
pa.txt chunk.ipa.model
指定されたパスが見つかりません。

C:\Windows\system32>..\bin\cabocha-model-index -f SHIFT-JIS -t SHIFT-JIS ne.ipa.
txt ne.ipa.model
指定されたパスが見つかりません。

C:\Windows\system32>echo SHIFT-JIS 1>charset-file.txt

実行ディレクトリが**C:\Windows\system32**になってしまうのだ。 そこで、バッチの先頭で実行ディレクトリに一旦移動する必要がある。それが上で追加したcd /d %~dp0だ。

これは他のバッチファイルでもよくハマるので、なんとなく心の片隅に入れとくとよい。

ちなみに毎回管理者として実行するには、 バッチのアイコンを右クリック→プロパティ→ショートカット→詳細設定→管理者として実行にチェック

Hudson/JenkinsでVisual Studioプロジェクトのビルドをする – MSBuild Plugin

Hudson/JenkinsでVisual Studioのプロジェクトをビルドしたい、そんなとき。
MSBuild Pluginを使うよ。

MSBuild Pluginの導入

「Hudsonの管理」→「プラグインの管理」→「利用可能」タブ→
Hudson MSBuild Plugin」にチェックを入れて「インストール」
インストールが完了したら、「ジョブが実行中でなければ再起動」ボタンを押してHudson/Jenkinsを再起動。

MSBuild Pluginの設定

導入ができたら、プラグインの設定をする。

「Hudsonの管理」→「システムの設定」

MSBuild Builderの項目が新たにできているので、nameとpathを指定する。
nameは、ジョブの設定時、MSBuildの選択肢として出てくるのでわかりやすい名前をつける。Path To msbuild.exeにはmsbuild.exeの場所を指定すればよい。
たいていの場合、C:\WINDOWS\Microsoft.NET\Framework\[version]\MSBuild.exe 
私の環境では以下のバージョンがあったよ。

  • v1.0.3705
  • v1.1.4322
  • v2.0.50727
  • v3.0
  • v3.5
  • v4.030319

設定例

とりあえず、最新のだけ使うので
name: v4.030319
path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe
と入力して保存。必要に応じて他のバージョンも追加できる。

ジョブの設定

「ビルド手順の追加」で
Build a Visual Studio project or solution using MSBuild.
を追加

  • MsBuild Versionはプルダウンメニューから使用するMSBuildを選ぶ。上のシステム設定でnameに指定したものが一覧に出る
  • MsBuild Build Fileにはビルドしたいプロジェクトの.projか.slnのパスを指定
  • Command Line ArgumentsにはMSBuildのオプションを設定

設定例

MsBuild Version: v4.030319
MsBuild Build File: C:\hudson\jobs\ジョブ名\workspace\プロジェクト名\プロジェクト名.sln
Command Line Arguments: /t:Rebuild /p:Configuration=Release

私の場合gitプラグインでプロジェクトをHudson/Jenkinsワークスペース以下にcloneして来てるので、cloneしてきたソリューションファイルの場所を指定しているよ。
Command Line Argumentsでリリース版のみをリビルドするよう設定。/t:は/target:、/p:は/property:の省略値。/t:cleanとかを組み合わせればクリーンビルドとかもできるね。
これでVC++のプロジェクトがビルドできたよ。

MSBuildの詳しい使い方

コマンドラインでUTF-8テキストのBOMを追加したり削除したりする

BOM(バイト順マーク)を削除することはよくあるんだけど、つけたことってあまりなかった。
エディタの機能を使えばできるものもあるけど、大量なファイルをいちいちエディタで開いて処理するわけにもいかないので、コマンドでまとめて処理したい、そんなとき。
結論としては、uconvを使えば割と手軽にBOMを付けたり消したりできる。
「BOMのマークの文字なんだったっけ?」とか考えなくてもよい方法

BOMの確認

fileコマンドを使う。

$ file *.cpp
foo.cpp:   UTF-8 Unicode text
bar.cpp:   UTF-8 Unicode (with BOM) text

BOMが付いてるテキストは「(with BOM)」となるので、エディタなどで開かずにBOMが付いてるかどうかを確認できる。

現在のディレクトリ以下にあるC++のソースファイル(c,h,cpp,hpp)でBOMが付いてないファイルのファイル名を取り出すには

find . -type f \( -name '*.[ch]' -o -name '*.cpp' -o -name '*.hpp' \) -exec file {} + | grep -v "(with BOM)" | cut -d: -f1

BOMが付いてるファイル名を取り出すにはgrepの-vを取って、

find . -type f \( -name '*.[ch]' -o -name '*.cpp' -o -name '*.hpp' \) -exec file {} + | grep "(with BOM)" | cut -d: -f1


BOMの追加と削除

uconvを使う。
詳しくは、International Components for Unicode http://site.icu-project.org/
debianの場合最初から使えたけど、ない人はaptitudeでlibicu-dev辺りを入れればいいんだと思う。Windowsのバイナリもある。

BOMを追加する

uconv -f utf-8 -t utf-8 --add-signature foo.cpp > foo_bom.cpp

BOMを削除する

uconv -f utf-8 -t utf-8 --remove-signature bar.cpp > bar_nobom.cpp

出力ファイルに入力ファイルと同じ名前を指定して消してしまわないように注意。
-fと-tはプラットホームのエンコーディングがutf-8以外の場合は省略するとうまくいかないことがあるので明示してる。
localeコマンドを実行してLANG=ja_JP.utf8とかだったら-f,-tオプションは省略できるよ。

上のBOMの確認の例と組み合わせれば、お手軽にまとめて変換できるね。

なんでBOMつけようとか思ったの

Windowsにつけろって言われたから。

VC++はBOM付きUTF-8

UTF-8はバイト順に依らないので、本当はBOMは要らないのだけどWindowsのVisual Studio C++では、BOM付きじゃないとUTF-8で書かれてるかどうか判断できないらしい。
ソースにマルチバイト文字を含んでる場合にはコンパイル時にC4819という警告が出て、正しく動作しないことがある。

ソースコードにUTF-8を使う場合はBOMつきのみサポート。

gccはどっちでもよい

でもgccはBOMどうなの?と思ったらBOM付きで問題なくコンパイルできる。この辺で直ったようだ
BOM付けてはいけないみたいな文書を見かけるが、イマドキは意外と付いててもなんとかなる。

Gitプロトコルでリモートリポジトリにアクセス

普段はssh経由でgitを使っているのだけれどhudsonでgit cloneするときにちょっと困ったので、認証の要らないGitプロトコルを使ってみることにしたよ。

hudsonでリモートのgitリポジトリからソース取って来ようとしたのだけれど、ssh経由なのでビルド自動実行時にパスワードプロンプトが出ちゃってうまくいかない。
そこで認証のいらないGitプロトコル(git://)でアクセスできるようにしてみたよ。
最初にgitosisというのを試そうと思ったのだけど、プロジェクトを管理する方法煩雑になりそうだったので、git-daemonだけでなんとかすることにした。ちなみにdebian。

目標

  • リポジトリの取得(git clone)はssh経由でなくてもできるように、git-daemonを動かしてgitプロトコルでアクセスできるようにする。
  • 日々の開発でpushする場合はssh経由で行う。


gitリポジトリの作成

gitサーバーで作業。

リポジトリサーバー名:sever
公開リポジトリの場所:/home/repos/

とするよ。ユーザー別に管理したかったので公開リポジトリの下に各ユーザーのディレクトリを作ったよ。僕用のディレクトリはskmkという名前。

$ cd /home/repos/skmk

まずここに管理用のbareリポジトリを作るよ。

$ mkdir hoge.git
$ cd hoge.git
$ git init --bare --shared=true
Initialized empty shared Git repository in /home/repos/skmk/hoge.git/
$ echo "Project hoge" > description
$ touch git-daemon-export-ok

descriptionを書き換えたのは、これをやらないとgit pushするときに
*** Project description file hasn’t been set
と怒られてpushできなかったからだよ。
git-daemon-export-okという空のファイルをtouchコマンドで作ったね。これをhoge.gitの直下に置くことで、このリポジトリは公開用という印になるんだよ。

gitリポジトリへpush

開発マシンでの作業
ローカルの開発マシンからpush。今回は新規に作ったものをサーバーへpushするよ。日々の開発用なのでssh経由で。

$ mkdir hoge_devel
$ cd hoge_devel
$ git init
$ touch hoge.txt
$ git add .
$ git commit -a -m"initial import"
$ git add remote origin ssh://server/home/repos/skmk/hoge.git
$ git push origin master
skmk@server's password:
Counting objects: 3, done.
Writing objects: 100% (3/3), 206 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To ssh://server/home/repos/skmk/hoge.git
* [new branch] ? ? ?master -> master


git-daemonを動かす

gitサーバーで作業。
あとはgit-daemonを動かす。まずは以下のコマンドで試してみるよ。

$ sudo -u gitosis git-daemon --base-path=/home/repos/ --reuseaddr --verbose /home/repos/git

オプションについて簡単な説明。

  • あらかじめ作ったgitユーザーでdaemonを実行する。僕の場合面倒だったので、aptitude install gitosisすると自動で作られるgitosisユーザーを流用して動かしたの。だから -u gitosisとなってるのでした。
  • –base-pathは公開リポジトリのベースとなる場所を指定
  • –reuseaddrをつけておくと古いコネクションのタイムアウトを待たないので、設定調整しながら動かしたり止めたりするときに便利だよ。
  • –export-allをつけるとgit-daemon-export-okの有無に関わらず全部公開されるよ。
  • –enable-server=receive-packをつけるとgitプロトコルでpushできるようになる。ただし誰でも認証なしで好き勝手にpushできちゃうので注意


gitプロトコルを使ってgit-cloneしてみる

開発マシンなど任意のサーバで作業。
git-daemon動かした状態で、ローカルマシンとかでgitプロトコル使ってgit cloneしてみる。

$ git clone git://server/skmk/hoge.git

–base-pathを設定してgit-daemon起動したので、sshのようにフルパス書かなくても、サーバーの後ろに/home/repos以下のディレクトリから書けば大丈夫だよ。
git-daemon-export-okを作るのを忘れていると

Initialized empty Git repository in /home/skmk/test/hoge/.git/
fatal: The remote end hung up unexpectedly

となってcloneできないよ。

xinetdでgit-daemonを動かす

gitサーバーで作業。
うまく動いたらスーパーサーバーデーモンにgit-daemonを登録するよ。さっき動かしたgit-daemonは止めてね。
xinetdの例で説明。xinetdがない場合はaptitude install xinetdでインストールしてね。
他のファイルに倣って、/etc/xinetd.d/gitというファイルを作成。

# description: git daemon 
# This is the tcp version.
service git
{
    disable        = no
    socket_type    = stream
    protocol    = tcp
    user        = gitosis
    wait        = no
    server        = /usr/bin/git-daemon
    server_args    = --base-path=/home/repos/git --inetd --verbose
    log_on_failure    += USERID
}

port = 9418は/etc/servicesに入ってたから、なんとなく略してみた。
それでは、xinetdを再起動してみます。

# cd /etc/init.d
# ./xinetd restart
Stopping internet superserver: xinetd.
Starting internet superserver: xinetd.

これで、他のサーバーからgitプロトコルでgit-cloneできるはず。お疲れ様でした。

一度に複数のリモートリポジトリにgit pushする方法

コマンドひとつで複数のリモートリポジトリにgit pushしたい、そんなとき。

作業中リポジトリの.git/configを直接編集して、push先のurlを複数書いちゃう。
試したのはgit version 1.5.6.5だよ。

[remote "foobar"]
        url = /hoge/foo.git
        url = /hoge/bar.git

あとはリモートリポジトリfoobarにpushすれば、foo.gitとbar.gitにpushされるよ。
でもgit remote -vとかgit showしたときに

warning: Remote origin has more than one URL

って怒られるけどね。
git config -l なら内容を確認できるよ。

remote.foobar.url=/hoge/foo.git
remote.foobar.url=/hoge/bar.git

わけわかんなくなるから複数登録するときはpush用にした方がいいかもね。

Visual Studio C++でCppUnitをビルドできない、そんなとき

CppUnitをWindowsでも使うことになってビルドを試みたのだけれど、結構ハマったので、ビルド方法をメモっておくよ。

WindowsでCppUnit使いたいのだけれど…

C++アプリケーションの効率的なテスト手法(CppUnit編) - @IT
ここを参考にMicrosoft Visual Studio VC++でビルドをしてみた。

  • VC++ 2010 Express プロジェクトファイル(.dsw)変換に失敗
  • VC++ 2008 Express リンクエラーでビルド失敗

となって、うまくいかない・・・
試行錯誤の結果、うまくビルドできたので以下手順を説明するよ。

準備

以下のものを用意する

VC++は別々のディレクトリにインストールされるので共存できるよ。

ビルド手順

cppunit-1.12.1.tar.gzはあらかじめ解凍ソフトなどで解凍しておく。

VC++ 2008でCppUnitTestMainプロジェクトを開く

VC++2008を起動
●「ファイル」→「開く」→「プロジェクト/ソリューション」
cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.dswを選択
(cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.dspでもよい)

プロジェクトの変換

●「変換してこのプロジェクトを開きますか?」と聞かれるので「すべてはい」

VC++2010じゃなくてVC++2008で使いたい人は→後述へ飛ぶ

プロジェクトの依存関係を外す

●「プロジェクト」→「プロジェクトの依存関係」
プロジェクト「CppUnitTestMain」を選択し、依存先のcppunit、cppunit_dllに入ってるチェックを外して「OK」

ソリューションファイルの保存

●ソリューションエクスプローラで
「ソリューション ‘CppUnitTestMain’ (3プロジェクト)」と書いてあるところを選択して、「ファイル」→「CppUnitTestMain.slnを保存」
ソリューションエクスプローラが見つからないときは「表示」→「ソリューションエクスプローラ」
●「ファイル」→「終了」でVC++2008の役目はこれでおしまい。

VC++ 2010でCppUnitTestMainプロジェクトを開く

VC++ 2010を起動
●「ファイル」→「開く」→「プロジェクト/ソリューション」
cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.slnを選択

VisualStudio変換ウィザード

自動的にウィザードが起動する。基本的に全部そのまま「次へ」でOK

●バックアップ作成の選択「変換前にバックアップを作成する」を選択すると.oldをつけたバックアップが作成される。
再びVC++2008で開きたいときなどに必要になるよ。

●「変換の完了」が出たら「閉じる」。「ウィザードが閉じたとき変換ログを表示する」にチェックが入ってると変換レポートが表示される。

プロジェクトのプロパティ設定

cppunit,cppunit_dllプロジェクトについて確認すべき点は2点。

  • 「構成プロパティ」→「全般」→「ターゲット名
  • 「C/C++」→「コード生成」→「ランタイムライブラリ

これらに気をつけて各プロジェクトのプロパティを以下の表のように設定する。

プロジェクト 構成 ターゲット名 ランタイムライブラリ
cppunit Debug $(ProjectName)d マルチスレッドデバッグ(/MTd)
Release $(ProjectName) マルチスレッド(/MT)
cppunit_dll Debug cppunitd_dll マルチスレッドデバッグDLL(/MDd)
Release $(ProjectName) マルチスレッドDLL(/MD)
CppUnitTestMain Debug $(ProjectName) マルチスレッドデバッグ(/MTd)
Release マルチスレッド(/MT)
DebugDLL マルチスレッドデバッグDLL(/MDd)
ReleaseDLL マルチスレッドDLL(/MD)

プロジェクト依存関係の設定

「プロジェクト」→「プロジェクトの依存関係」でCppUnitTestMainプロジェクトを選択、
 依存先

  • cppunit
  • cppunit_dll

にチェックを入れてOK。

バッチビルドの実行

●「ツール」→「設定」→「上級者用の設定」を選択
メニューバーに「ビルド」項目が現れる
●「ビルド」→「バッチビルド」→「すべて選択」→「ビルド」
一度「クリーン」してから「ビルド」すると安心かもね。

DLL版はコンパイル中にC4251のwarningが結構出るけど特に問題ないと思う。
CppUnitTestMainがビルドできると、自動でテストの実行も行ってるよ。

●8つの全部が正常終了すればOK。

CppUnitを使ったプロジェクトを作るときにはCppUnitTestMainのプロパティを参考に設定するといいよ。

うまくいかない?

設定をもう一度見直してみよう。とくにランタイムライブラリの設定だね。
VC++2008でプロジェクトの依存関係を外さずにCppUnitTestMain.slnを保存してしまった場合は、
VC++2010でこれを開くと「プロジェクト」→「プロジェクトの依存関係」から編集ができなくなるよ。
CppUnitTestMainの「共通プロパティ」→「Frameworkと参照」ですべての「参照を削除」すれば編集できるようになるよ。

VC++ 2008でビルドしたいんだよ

って言う人は、上記手順のCppUnitTestMain.dswを開いた後、「プロジェクトの依存関係」は外さずに、
CppUnitTestMainのプロパティで
「リンカー」→「全般」→「リンクライブラリの依存関係」をすべての構成(Debug,Release,DebugDLL,ReleaseDLL)で「いいえ」に設定。
これをいいえにしないと、プロジェクト依存関係にあるcppunit,cppunit_dllのどちらもリンクしようとして競合して失敗する。

リンクライブラリの依存関係を「はい」のまま、プロジェクトの依存関係を外して、先にcppunitとcppunit_dllをビルドしておいて、CppUnitTestMainを単体でビルドするとうまくリンクできるはずだ。
あとは上のVC++ 2010のプロパティ設定の表のようにランタイムライブラリを適切に設定する。ターゲット名は修正しなくてよい。

(以下追記 2011/04/16)

ていうか、プロジェクトファイルくれよ

というリクエストがありました。ですよね。欲しいですよね。

cppunit-1.12.1 の Microsoft Visual Studio C++ 2010 Express用プロジェクトファイル

cppunittest-1.12.1-vcxproj.zip
↑このzipを展開すると、以下の7つのファイルが入ってるよ。

    examples\cppunittest
  • CppUnitTestMain.sln
  • CppUnitTestMain.vcxproj
  • CppUnitTestMain.vcxproj.filters
  • src\cppunit
  • cppunit.vcxproj
  • cppunit.vcxproj.filters
  • cppunit_dll.vcxproj
  • cppunit_dll.vcxproj.filters

この4つをcppunit-1.12.1以下の対応するディレクトリに置いてCppUnitTestMain.slnをダブルクリックで起動すればそのまま使えるはず。

ちなみにVisual Studio C++のProfessional版なら、上の手順みたく頑張らなくてもプロジェクトファイル変換できたよ…

Hudson Growl Pluginでビルド結果をGrowlへ通知

Hudsonのビルド結果をGrowlに通知するプラグインGrowl Pluginを試してみたよ。

HudsonGrowl

Hudson Growl pluginのインストール

  1. Hudsonの管理>>プラグインの管理>>利用可能タブを選択
  2. Hudson Growl pluginを探してチェックを入れる
    (利用可能タブに見つからない場合はインストール済みタブを確認してみる。)
  3. 最下部の「インストール」ボタンを押す
  4. インストールが完了したら「ジョブが実行でなければ再起動」ボタンを押してHudsonを再起動
  5. 再起動したらHudsonの管理>>プラグインの管理>>インストール済みタブでHudson Growl pluginが有効化されていることを確認

Hudson側の設定

システム設定

Hudsonの管理>システムの設定>Global Growl Settingsにパスワードを入力して保存する。ここでは仮にpasswordと入力する。
Growl側で既にパスワード設定している場合はそのパスワードを入力すること(Growl側のパスワード設定については後述)。

Global Growl Setting
「失敗したときと回復したときだけ通知」する場合は「Only Growl on Failure or Recovery?」にチェック。

プロジェクトの設定

各プロジェクトの設定でビルド後の処理に「Growl」が追加されてるのでチェックを入れ、通知するマシンのIPアドレスを入力する。
私の場合、VirtualBoxのゲストOSでhudsonを実行していて、ホストOSがMac OSX(Growlの通知先)なので、192.168.56.1と入力したよ。

Hudson Growl IP Setting


「高度な設定」を押すと「失敗したときと回復したときだけ通知」するかどうかを選べるよ。

Growl側の設定

MacのGrowl1.2.1の場合で説明するよ。

  1. Growlのアイコンをクリックして「Open Growl Preferences…」を選択するかMacの「システム環境設定」→「その他」→「Growl」を選択
  2. 「ネットワーク」を選択して、「受信される通知を聞く」にチェック、「リモートアプリケーション登録を許可」にチェック。
    サーバパスワードにパスワードpasswordを入れる。このパスワードはHudson側のシステム設定で入力したパスワードだよ。
    既にGrowl側にパスワードが設定されている人は、そのパスワードをHudson側のシステム設定で指定してね。

Growl Network Setting

通知の確認

ビルド実行してみる。初めて実行したときはキーチェーンがアクセスを許可するか聞いてくるので、「許可」あるいは「常に許可」
GrowlKeyChain

以下のようにプロジェクト名、ステータス、ビルド番号、URLが通知される。
GrowlNotify

通知スタイルを個別に設定したい場合は、Growlの設定で「アプリケーション」を選択すると「Growler」というのがリストにいると思うので、これを選んで設定を押すと編集できるよ。
Growler
重要なプロジェクトの場合他の通知に紛れて見逃さないように、目立つ通知方法にするといいかもよ。sticky notificationにするとかね。
Hudsonの失敗ビルドのRSSをSafariにブックマークしてGrowlSafari(GrowlをダウンロードするとExtraに入ってる)で通知とかやってたんだけど、こっちの方がSafariの他の通知と切り分けられるのでいいね。

timeコマンドの結果をファイルへ出力する

timeコマンドの結果をファイルへ出力するやり方を間違えたのでメモ。
ちなみにbash。

やること

a.outを実行したときの出力とそれにかかった時間をlogfileへ出力する

ダメな例

time ./a.out > logfile
time ./a.out | tee logfile

上がまるっとリダイレクトしちゃうケースで、下はteeで標準出力にも表示しながら実行するケース。
散々時間かけて実行したのに、後でlogfileを見てみたらガッカリ。timeの出力だけはlogfileには書き込まれてない。

timeの結果は標準エラー出力

なのでした。
標準エラー出力も標準出力と一緒にlogfileに書き出さないとダメだね。

まだダメな例

というわけで標準エラー出力も併せてリダイレクトするよ

time ./a.out >& logfile
time ./a.out > logfile 2>&1
time ./a.out 2>&1 | tee logfile

しかし、これもダメ。
./a.out >& logfile
./a.out > logfile 2>&1
./a.out 2>&1 | tee logfile
といったtimeコマンド後ろに書いた「a.outコマンドの実行結果をエラー出力も含めてlogfileに書き出す」のtimeを計っちゃうわけ。だからtimeコマンドの結果はlogfileには書き込まれないよ。

OKな例

やりたい事は「time ./a.out」の出力をlogfileに書くんだから…

(time ./a.out) >& logfile
(time ./a.out) > logfile 2&>1
(time ./a.out) 2>&1 | tee logfile

括弧で囲んじゃえー。これでうまく行く。つまりサブシェルで実行するのです。
ちなみに最近のbashは>&じゃなくて&>でも大丈夫なのね。

コマンドをグループ化する{ ;}でもOKみたい。

{ time ./a.out ; } >& logfile
{ time ./a.out ; } > logfile 2&1
{ time ./a.out ; } 2>&1 | tee logfile

{ の後には必ずスペースを入れようね。スペースいれないと、-bash: syntax error near unexpected token `}’ って言われちゃうよ。あとセミコロン;も忘れずに。

/usr/bin/time

ところで、bash組み込みのtimeとは別にGNUのtimeコマンドというのもあるよ。/usr/bin/timeとフルパスで書いてやると実行できる。
これもbash組み込みのtime同様、標準エラー出力に結果を出力するんだけど、サブシェルで実行しなくても、

/usr/bin/time ./a.out >& logfile
/usr/bin/time ./a.out > logfile 2>&1
/usr/bin/time ./a.out 2>&1 | tee logfile

と、括弧つけなくても大丈夫。もちろん括弧つけてもいい。
それに、ちゃんとファイル出力オプション-oがあるよ。

/usr/bin/time -o logfile ./a.out

でもこれだとtimeの結果しか書き込まれないので、追記オプション-aをつけて

/usr/bin/time -a -o logfile ./a.out > logfile

とやればいいっぽい(おそらく「./a.out > logfile」のtimeなんだろうけど)。

bashのtimeより詳細なデータが出るし、出力フォーマットの指定とかいろいろ柔軟に操作できるコマンドなので覚えておくとよいかもしれないね。
詳しくはman timeで。bashのtimeについてはhelp timeで。

改行を置換したい

改行を置換したいときのメモだよ。
改行コードCR+LFからCRを取り除くとかいう話ではなくて、改行そのものを置換したいときのメモ。CRは入ってない前提。
コマンドのバージョンとか動作環境によってはうまく動かないこともあるかもしれない。

【例】改行をカンマ’,'に置換する

A
B
C

というファイルを

A,B,C

のように書き換えたいという例でお話しする。


エディタで

Emacs

M-x replace-string Replace string:

のところでC-q C-jと入力すると改行コードを引用できて、コマンド行が改行される。Enterで決定すると

Replace string with ^J with:,

となる。改行は^Jで表されている。置換先のカンマ’,'を入力してEnterで決定すれば、現在行以下の改行がすべてカンマに置換される。

Vim

:%s/\n//g

コロンを押してコマンドモードにして%でファイル全体を指定。
%の代わりに1,$と書いてもよい。1行目から最後の行までという意味。
もし置換ではなく改行削除して行を連結するだけなら、J(Shift+j)を押しっぱなしってのもひとつの手。

コマンドラインで

エディタを開かずにコマンドラインだけで済ませたいときもある。
filename は改行を含むテキストファイル。
標準出力に表示するだけなので、必要があれば > newfilename とかやって新しいファイル名を指定して出力してね。
元と同じファイル名に吐き出して消しちゃうっていうのはやりがちなミスなので気をつけて。

tr

以上は代表的なエディタ上での置換方法だったけど、コマンドラインで済ませたいときはtrがいちばんラクかも。

tr '\n' ',' < filename
あるいは
tr '\012' ',' < filename

改行を削除したいときはーdオプション。

tr -d '\n' < filename
あるいは
tr -d '\012' < filename

awk

awk -F\n -v ORS=','  '{print}'  filename

-Fで区切り文字指定、 -v ORSで出力の区切り文字指定。

sed

sedは1行ごとに処理するのは得意なんだけど、改行をまたぐ処理はわりと面倒くさい。
たとえば上の例とは逆にカンマを改行に変えるのであれば

sed 's/,/\n/g' filename
あるいは
sed 's/,/\        ←バックスラッシュの後で普通にEnterを押す
> /g' filename

でうまくいく。1行目のバックスラッシュのあとに普通に改行ボタン押せば次の行にプロンプトが出るので、続きを入力すればよい。
ところが例題の処理をやろうとして

sed 's/\n/,/g' filename
あるいは
sed 's/\
> /,/g' filename

と書いてもうまくいかないワケ。
じゃあどうするかというと、Nコマンドというのを使えば次の行も読み込めるので、これを最後までループでまわして全体を読み込んでから置換する。

sed ':loop; N; $!b loop; ;s/\n/,/g' filename
あるいは
sed ':loop; N; $!b loop; ;s/\
> /,/g' filename

ただGNUのsedだとうまくいくけど、BSDのsedだとこのやり方でもうまくいかないかも。
[参考]sed の使い方 4.3 改行をすべて削除

Perl

perl -pe 's/\n/,/g' filename

ruby

ruby -pe 'gsub(/\n/,",")'  filename

必要なときだけ計算する

Xerces-C++でXMLからDOMツリー作ったりしてるんだけれど
最近パフォーマンス気にするようなコード書いてなかったので、非常に基礎的なところでハマってた。
テスト用の短いXMLだと問題なかったんだけど、本番用のXML食わせたらforの中では大したことやってないのに5分以上かかっちゃうわけ。

問題のコード

//...
DOMNodeList* nodelist = document->getElementsByTagName(tagname);
//...
for(int i=0;i< nodelist->getLength();i++){
    //....
}

ここだけ抜き出せばもうわかると思うけど、forの終了条件の中で nodelist->getLength() してる。こいつの計算コストがけっこう高い。
nodelist->getLength()をnodelist->getLength()回呼び出したらそりゃ遅い。
変数の名前考えるのが苦手なので、できるだけ変数宣言しないように書いてたらこうなったってのもある。

次のように書き直すべき

//...
DOMNodeList* nodelist = document->getElementsByTagName(tagname);
//...
for(int i=0,num=nodelist->getLength();i<num;i++){
    //....
}

今回作ってたケースでは、途中でリストのサイズが変わったりしないので最初に1回計算すればそれでよかったわけ。
だからforの初期条件のところで、あらかじめ計算するようにしたよ。
これだけで5分以上かかってたコードが1秒ちょっとで終わったよ。

ちなみに、Xercec-c++(2.8.0)のsrc/xercesc/dom/impl/DOMNodeListImpl.cppには、ご丁寧に

// this implementation is too stupid – needs a cache of some kind.

って書いてあるよ。