私はガラケーを契約していますが、普段はSIMフリースマホと格安SIMを持ち歩いています。あまりこちらから誰かに電話することもないので、これで十分なのです。
しかし、キャリアのメールに来た場合にすぐに連絡できないと困るので、キャリアメールは全てGmailに転送する設定にしています。別にこれであんまり困ったことはありませんでした。
ところが最近になって、ドコモの携帯を使う友人から「お前メアド変えた?何か送信できないって返ってくるんだけど」と言われました。
んなバカな、現に私の携帯には友人からのメールが届いています。
どういうメールが返ってくるのか見せてもらったところ、宛先には私のGmailのアドレス、送信元は友人のメールアドレス、そして、
The address specified is not a valid RFC-5321 address.
というメッセージが。
RFC-5321は、メールアドレスの形式の基準を定めた物。どうやら友人のメールアドレスが、これに反しているためGmail側で「不正なメールアドレスの形式」と判断して跳ね返していたようです。
どうもドコモはドコモでRFC-5321とは異なる独自のメールアドレス形式を許可しているようで、Gmailも最近まではローカルルールでこれを許可していたようですが、厳格化したようです。
わかってしまえば大した原因でもないんですが、これをITに詳しくない友人にどう伝えようか。メアド変えてくれって言うわけにもいかないしなあ。
いろいろあって、エクセルで業務を行うことになり、VBAでマクロを組むことに。
以前VBAでプログラム組んだことがあったので特に問題はなく、とりあえず作ってみました。
VBAは何も考えないで作るとすげー遅くなるので、いくつか高速化テクニックがあります。
・セルを二次元配列で扱う
大量のセルを扱う場合、一つ一つ見ていくとその都度セルに対する評価が走るため、参照するセルが多くなればなるほどマクロは遅くなります。
そこで、Rangeオブジェクトを二次元配列として扱うことで、セルを参照せずメモリ上で全て処理を行うので段違いに早くなります。
・ワークシート関数を使う
例えばあるシートの条件に合うセルを検索する場合。検索するセルが100や200なら気にすることでもありませんが、1000や10000など多くなると二次元配列にしても遅くなります。
エクセルVBAではエクセルのワークシート関数を呼び出すことが出来ます。テーブルの左端にキーがあり、合致するキーの右隣のセルを参照したいなどといった場合はVLOOKUP関数を使うことで実現できます。
VBAの関数でセルを検索する場合Find関数がありますが、ワークシート関数より性能が劣るので、ワークシート関数が使える場合は積極的に使っていくのがいいと思います。
そして、ここからが本題。
上記2つの高速化テクニックを使ってマクロを組んでいましたが、なぜかエラーが発生して止まってしまうという現象が報告されました。
自分でテストした分には問題なかったんだけど。おかしいなあということでエラーになったデータをもらって実際にやってみたところ、エラーが再現しました。
エラー箇所はズバリ、二次元配列を引数にしてワークシート関数を呼び出しているところ。
WorksheetFunction.Transpose(二次元配列)
ここでエラーになっていました。Transpose関数を呼び出して、二次元配列の一次元と二次元を入れ替える、エクセルでイメージすると表の行列を入れ替える処理を行っていました。テストでは何事もなかったのですが。
VBAのエラーはわけわからないのが多く、正直エラーだけでは何もわからなかったのですが、調べてみると、どうやらTranspose関数に渡せる二次元配列は、要素数が65536までという制限があるらしく、実際のデータではこれを大幅に超えていたため、エラーとなっていました。
そういうことはもっとわかりやすく書いといてくれ!
これに関しては、自力で二次元配列の次元を入れ替えるメソッドを作成することで回避できましたが、もう一つ罠が潜んでいました。
報告されたエラーはこれだけでしたが、また来ると面倒なのでいろいろ試してみたところ、検索文字列に一致するセルを検索するところで、あるはずの文字列が見つからずエラーとなっていました。
これは同じくワークシート関数のMatch関数を使って検索を行っていたのですが、どうやら検索文字列が極端に長い場合見つけることができないようです。
どこにもそんなこと書いてないと思うんだが…と思い、マクロではなくシートに直接Match関数を書いてみると、検索できる文字列は255文字までというエラーメッセージが……。
どうやらマクロ云々ではなく、エクセル関数の仕様上渡せる一つの文字列の上限は255文字までのようです。マクロから呼び出す場合もこの制限に引っかかるようです。
じゃあ仕方ない、性能は落ちるけどVBAのFind関数使うか…と思ったら、こっちも検索できる文字列は255文字までのようで、あっさりエラーとなりました。
意味ねーじゃねーか!
今回はどうしても255文字以上を検索する可能性が排除できないため、代替手段を考えねばなりません。検索対象は10000セル以上あり、検索回数も10000回以上あるので、ループで回すといつまでも終わらない。どうしたもんかと考えた末、VBAのFilter関数を使うことにしました。
Filter関数は一次元配列を指定した文字列を含む要素に絞り込む関数です。ただし、完全一致検索はできず、戻り値は一次元配列。これは検索文字列が255文字以上でも使えるため、これで絞り込んだ後戻ってきた一次元配列をループして完全一致する文字列を探すという方法を取りました。二次元配列から一次元配列に直すという処理を追加する羽目になりましたが。
エラーは回避できたものの、性能は劣化してしまいました…。
ワークシート関数を使うとエクセルのマクロはもっと早くなりますが、その制限もワークシートで使う場合と同じようです。使えるところと使えないところを見極めて使う必要があります。
以前VBAでプログラム組んだことがあったので特に問題はなく、とりあえず作ってみました。
VBAは何も考えないで作るとすげー遅くなるので、いくつか高速化テクニックがあります。
・セルを二次元配列で扱う
大量のセルを扱う場合、一つ一つ見ていくとその都度セルに対する評価が走るため、参照するセルが多くなればなるほどマクロは遅くなります。
そこで、Rangeオブジェクトを二次元配列として扱うことで、セルを参照せずメモリ上で全て処理を行うので段違いに早くなります。
・ワークシート関数を使う
例えばあるシートの条件に合うセルを検索する場合。検索するセルが100や200なら気にすることでもありませんが、1000や10000など多くなると二次元配列にしても遅くなります。
エクセルVBAではエクセルのワークシート関数を呼び出すことが出来ます。テーブルの左端にキーがあり、合致するキーの右隣のセルを参照したいなどといった場合はVLOOKUP関数を使うことで実現できます。
VBAの関数でセルを検索する場合Find関数がありますが、ワークシート関数より性能が劣るので、ワークシート関数が使える場合は積極的に使っていくのがいいと思います。
そして、ここからが本題。
上記2つの高速化テクニックを使ってマクロを組んでいましたが、なぜかエラーが発生して止まってしまうという現象が報告されました。
自分でテストした分には問題なかったんだけど。おかしいなあということでエラーになったデータをもらって実際にやってみたところ、エラーが再現しました。
エラー箇所はズバリ、二次元配列を引数にしてワークシート関数を呼び出しているところ。
WorksheetFunction.Transpose(二次元配列)
ここでエラーになっていました。Transpose関数を呼び出して、二次元配列の一次元と二次元を入れ替える、エクセルでイメージすると表の行列を入れ替える処理を行っていました。テストでは何事もなかったのですが。
VBAのエラーはわけわからないのが多く、正直エラーだけでは何もわからなかったのですが、調べてみると、どうやらTranspose関数に渡せる二次元配列は、要素数が65536までという制限があるらしく、実際のデータではこれを大幅に超えていたため、エラーとなっていました。
そういうことはもっとわかりやすく書いといてくれ!
これに関しては、自力で二次元配列の次元を入れ替えるメソッドを作成することで回避できましたが、もう一つ罠が潜んでいました。
報告されたエラーはこれだけでしたが、また来ると面倒なのでいろいろ試してみたところ、検索文字列に一致するセルを検索するところで、あるはずの文字列が見つからずエラーとなっていました。
これは同じくワークシート関数のMatch関数を使って検索を行っていたのですが、どうやら検索文字列が極端に長い場合見つけることができないようです。
どこにもそんなこと書いてないと思うんだが…と思い、マクロではなくシートに直接Match関数を書いてみると、検索できる文字列は255文字までというエラーメッセージが……。
どうやらマクロ云々ではなく、エクセル関数の仕様上渡せる一つの文字列の上限は255文字までのようです。マクロから呼び出す場合もこの制限に引っかかるようです。
じゃあ仕方ない、性能は落ちるけどVBAのFind関数使うか…と思ったら、こっちも検索できる文字列は255文字までのようで、あっさりエラーとなりました。
意味ねーじゃねーか!
今回はどうしても255文字以上を検索する可能性が排除できないため、代替手段を考えねばなりません。検索対象は10000セル以上あり、検索回数も10000回以上あるので、ループで回すといつまでも終わらない。どうしたもんかと考えた末、VBAのFilter関数を使うことにしました。
Filter関数は一次元配列を指定した文字列を含む要素に絞り込む関数です。ただし、完全一致検索はできず、戻り値は一次元配列。これは検索文字列が255文字以上でも使えるため、これで絞り込んだ後戻ってきた一次元配列をループして完全一致する文字列を探すという方法を取りました。二次元配列から一次元配列に直すという処理を追加する羽目になりましたが。
エラーは回避できたものの、性能は劣化してしまいました…。
ワークシート関数を使うとエクセルのマクロはもっと早くなりますが、その制限もワークシートで使う場合と同じようです。使えるところと使えないところを見極めて使う必要があります。
JavaでHttpURLConnectionを使用し、特定のサーバに次々とHTTPリクエストを投げて必要な情報を取得するツールを作成していました。
テスト的に動かしてみると、順調に動き出しましたが、しばらくすると、
java.net.BindException: Address already in use: connect
と例外を出力して止まってしまいました。
なんじゃあこりゃあ。
メッセージを見ると「アドレスはすでに使われています」ということだけど、どういうことだ・・・?
いろいろ調べてみると、どうやら連続して接続していたせいで、ソケットが閉じていないとのこと。
いやいや、使っていたクラスはHttpURLConnectionだけど、ちゃんとInputStreamもOutputStreamも閉じてるっちゅーねん。
そもそもTCPの仕様として、ポートを開いたらclose指示が出されても一定時間は待ち続けるとのこと。
だからプログラムの作りとして、連続でアクセスかけるようなことをしてはいけないのだそうだが、そんなこと言ってもプログラム作っちゃったし、作り直しするなら設計から別の仕様を考えることになっちゃうのでなんとかできないかと調べてみました。
第一に、Windowsのレジストリを書き換えて、一度に使用できるSocketを多くする。または、閉じるまでの待ち時間を短くする。
しかし今回はレジストリをいじるリスクが怖くて断念。
第二に、SO_LINGERの値をプログラムで指定し、closeしたら待たずに即座に閉じるようにする。
プログラム直すくらいでできるならそれでもいいかなと思ったけど、HttpURLConnectionはSocketを隠蔽していて外から触ることができない。これも断念。
しかたがないので、サーバに接続するところで上記例外をcatchし、しばらくsleepしてリトライする処理をしてお茶を濁すことに。
結局どういう処理をするのが正解だったんだろーか。
テスト的に動かしてみると、順調に動き出しましたが、しばらくすると、
java.net.BindException: Address already in use: connect
と例外を出力して止まってしまいました。
なんじゃあこりゃあ。
メッセージを見ると「アドレスはすでに使われています」ということだけど、どういうことだ・・・?
いろいろ調べてみると、どうやら連続して接続していたせいで、ソケットが閉じていないとのこと。
いやいや、使っていたクラスはHttpURLConnectionだけど、ちゃんとInputStreamもOutputStreamも閉じてるっちゅーねん。
そもそもTCPの仕様として、ポートを開いたらclose指示が出されても一定時間は待ち続けるとのこと。
だからプログラムの作りとして、連続でアクセスかけるようなことをしてはいけないのだそうだが、そんなこと言ってもプログラム作っちゃったし、作り直しするなら設計から別の仕様を考えることになっちゃうのでなんとかできないかと調べてみました。
第一に、Windowsのレジストリを書き換えて、一度に使用できるSocketを多くする。または、閉じるまでの待ち時間を短くする。
しかし今回はレジストリをいじるリスクが怖くて断念。
第二に、SO_LINGERの値をプログラムで指定し、closeしたら待たずに即座に閉じるようにする。
プログラム直すくらいでできるならそれでもいいかなと思ったけど、HttpURLConnectionはSocketを隠蔽していて外から触ることができない。これも断念。
しかたがないので、サーバに接続するところで上記例外をcatchし、しばらくsleepしてリトライする処理をしてお茶を濁すことに。
結局どういう処理をするのが正解だったんだろーか。
うちのシステムに不具合があったので修正しなければならなりました。
現象が再現し、修正個所はここだろうと目星をつけたものの、念のためログを確認していると、なぜかわけのわからないところでわけのわからない例外が発生していました。
なぜここでNumberFormatException???
NumberFormatExceptionは、文字列を数字に変換しようとしたが、数字に変換できる文字列ではなかった場合に発生する例外。
しかし例外が発生した場所はSimpleDateFormatを使って、文字列から日付型に変換していた部分。
全然数字とは関係ない気がするんですが・・・。
と思って調べてみたら、どうやらSimpleDateFormatは非スレッドセーフということが原因のようです。
理系学生日記
製造時には、別々のメソッドから共通で使われるし、毎回毎回同じインスタンス生成するのもバカバカしいし、Servletのメンバ変数にSimpleDateFormatの変数宣言しとけーと思っていたんですが、これが完全に誤りでした。バカは私でした。
事例に学ぶWebシステム開発のワンポイント(4):マルチスレッドのいたずらに注意―あなたのJSPやServletは大丈夫?― - @IT
Javaサーブレット メモ(Hishidama's Servlet Memo)
特に設定しない限り、Servletはマルチスレッドで動作します。
つまり、1つのServletインスタンスに複数のスレッドからアクセスがあるわけです。
あるスレッドがServletインスタンスのメンバ変数の値を書き換えたら、別のスレッドがその値を書き換えない保証はないわけです。あるスレッドで行われいる処理がそのスレッドで完結せず、他のスレッドの処理に影響を及ぼしてしまうのです。(簡単に言うとこれが非スレッドセーフということ。)
「SimpleDateFormatは非スレッドセーフ」の話はいつから言われているのかわからないくらい前から言われていることで、今更感が半端じゃないのですが、実際に我が身に起こったのでここに記録しておきます。
しかし現実問題として、マルチスレッドの問題というのは開発時には非常にわかりにくく、よっぽど注意してソースレビューをしたり、負荷テストをしっかりやらないと発見が遅れたり、発見しても原因がわからないこともあったりして結構面倒くさい問題なのです。
こういうこと知ってるのと知らないのとでは大きな差が出るんだろうなー。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
現象が再現し、修正個所はここだろうと目星をつけたものの、念のためログを確認していると、なぜかわけのわからないところでわけのわからない例外が発生していました。
なぜここでNumberFormatException???
NumberFormatExceptionは、文字列を数字に変換しようとしたが、数字に変換できる文字列ではなかった場合に発生する例外。
しかし例外が発生した場所はSimpleDateFormatを使って、文字列から日付型に変換していた部分。
全然数字とは関係ない気がするんですが・・・。
と思って調べてみたら、どうやらSimpleDateFormatは非スレッドセーフということが原因のようです。
理系学生日記
製造時には、別々のメソッドから共通で使われるし、毎回毎回同じインスタンス生成するのもバカバカしいし、Servletのメンバ変数にSimpleDateFormatの変数宣言しとけーと思っていたんですが、これが完全に誤りでした。バカは私でした。
事例に学ぶWebシステム開発のワンポイント(4):マルチスレッドのいたずらに注意―あなたのJSPやServletは大丈夫?― - @IT
Javaサーブレット メモ(Hishidama's Servlet Memo)
特に設定しない限り、Servletはマルチスレッドで動作します。
つまり、1つのServletインスタンスに複数のスレッドからアクセスがあるわけです。
あるスレッドがServletインスタンスのメンバ変数の値を書き換えたら、別のスレッドがその値を書き換えない保証はないわけです。あるスレッドで行われいる処理がそのスレッドで完結せず、他のスレッドの処理に影響を及ぼしてしまうのです。(簡単に言うとこれが非スレッドセーフということ。)
「SimpleDateFormatは非スレッドセーフ」の話はいつから言われているのかわからないくらい前から言われていることで、今更感が半端じゃないのですが、実際に我が身に起こったのでここに記録しておきます。
しかし現実問題として、マルチスレッドの問題というのは開発時には非常にわかりにくく、よっぽど注意してソースレビューをしたり、負荷テストをしっかりやらないと発見が遅れたり、発見しても原因がわからないこともあったりして結構面倒くさい問題なのです。
こういうこと知ってるのと知らないのとでは大きな差が出るんだろうなー。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
Javaでプログラミングをしてきておよそ8年になる私ですが、この業界の技術の進歩に追いつくことができず、いまだにわからないことも多い中、気が付けば次々と新しい技術や概念が現れています。
それに追いつくのも大事なのですが、昔からやっていることでもなかなか頭に入らないことも多いです。
その1つがJavaのcompareToメソッド。
オブジェクトの大小を比較するメソッドで、等しければ0。異なれば0未満か0よりも大きい数字を返してくれる。
ということはわかっているのですが、どっちが大きいと0未満でどっちが小さいと0よりも大きい数字なんだっけ?
と使おうとするたびにいちいち調べてしまうので、ここにメモっておきます。
A.compareTo(B)とした場合、
A = B ならば 0
A < B ならば 0未満
A > B ならば 0よりも大きい数字
で、いいんだよね…?
----
ゆりまぐろ 百合男子予備軍の学習帳
それに追いつくのも大事なのですが、昔からやっていることでもなかなか頭に入らないことも多いです。
その1つがJavaのcompareToメソッド。
オブジェクトの大小を比較するメソッドで、等しければ0。異なれば0未満か0よりも大きい数字を返してくれる。
ということはわかっているのですが、どっちが大きいと0未満でどっちが小さいと0よりも大きい数字なんだっけ?
と使おうとするたびにいちいち調べてしまうので、ここにメモっておきます。
A.compareTo(B)とした場合、
A = B ならば 0
A < B ならば 0未満
A > B ならば 0よりも大きい数字
で、いいんだよね…?
----
ゆりまぐろ 百合男子予備軍の学習帳
Linuxで、あるディレクトリに存在するファイルから、最新のファイルのみを取得する必要が出てきました。
たとえばこのようなディレクトリがあったとして、
/usr/local/hoge
その中にこのようなファイルが存在していたとします。
aaaa.txt 更新日時:2013年9月1日12時34分56秒
bbbb.txt 更新日時:2013年9月2日12時34分56秒
cccc.txt 更新日時:2013年9月3日12時34分56秒
この中で欲しいのは、最も更新日時が新しい「cccc.txt」です。
(Linux)指定したディレクトリで一番新しいファイルを探す を参考にさせていただいたのですが、こちらで紹介されている
LATEST_FILE="`ls -lt *.txt | head -n 1 | gawk '{print $9}'`"
この方法では、自分の環境ではなんと更新年が取得できてしまいました。
こちらの方法は、「ls -lt *.txt」で拡張子が.txtのファイルを更新日順に出力し、「head -n 1」で先頭行、つまり最新ファイルの行を取得し、「gawk '{print $9}'」でその行の9列目の文字列を取得しているというわけです。
つまり9列目にファイル名が存在するという決まりが無い限りはファイル名が取得できないのです。
自分の環境では9列目に更新年が表示されてしまい、ファイル名が取得できなかったのです。
10列目にファイル名が表示されるので、「gawk '{print $9}'」を「gawk '{print $10}'」に変えれば取得できるのですが、別の環境ではまた9列目に表示されるかもしれません。SEやPGといった職種は、できれば一度書いたプログラムに手を入れたくなく、別の環境に持って行っても最低限の設定を修正すれば同じ動きをしてほしいと常に考えるものだと思います。
9列目だろうが10列目だろうが、いずれにせよ行の最後にファイル名が出てくるということから、次に考えたのがこれ。
str=`ls -lt *.txt | head -1`;ary=(${str});cnt=${#ary[@]};echo ${ary[${cnt}-1]}
ちょっと長くてややこしいですが、;で改行すると多少わかりやすくなるかもしれません。
str=`ls -lt *.txt | head -1`
ary=(${str})
cnt=${#ary[@]}
echo ${ary[${cnt}-1]}
上から順に説明すると、
最新ファイルの行を変数strに格納。
変数strの中身を配列aryに格納。
配列aryの要素数を取得。
配列の最後の要素(=ファイル名)を出力。
という処理になります。これならls -ltで出力される内容が9列でも10列でも100列でも行の最後にファイル名が出る限り、ファイル名を取得可能です。ただファイル名1つ取るのに3つも変数を使っている。処理が多いとそれだけバグが入る可能性が多くなり、かえって面倒なので、最大限圧縮したのがこれ。
ary=(`ls -lt *.txt | head -1`);echo ${ary[${#ary[@]}-1]}
配列を作らなければならない以上、どうしても2段階になってしまいます。別に使えるんですが、なんとなくかっこ悪い。
「あ、そうだ、別に配列にしなくても、スペース区切りの最後の文字列を取得できればいいんだ」と考え、そして最終的にたどり着いた結果がこれ。
ls -lt *.txt | head -1 | sed -e "s/.* //g"
最新ファイルの行を取得し、sedコマンドで半角スペースを含む文字列を全部削除しています。
これで一応自分の望む結果が取得できたのですが、これだとファイル名に半角スペースが含まれていると取得に失敗しそうです。
要件に合わせて使いやすい方を使おうと思います。
【追記】
もっと楽に取得できる方法を教えてもらいました。
ls -1t *.txt | head -1
これだけでOK。1オプションがとても大事でした!lオプションで全情報出さなくても大丈夫ですね。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
たとえばこのようなディレクトリがあったとして、
/usr/local/hoge
その中にこのようなファイルが存在していたとします。
aaaa.txt 更新日時:2013年9月1日12時34分56秒
bbbb.txt 更新日時:2013年9月2日12時34分56秒
cccc.txt 更新日時:2013年9月3日12時34分56秒
この中で欲しいのは、最も更新日時が新しい「cccc.txt」です。
(Linux)指定したディレクトリで一番新しいファイルを探す を参考にさせていただいたのですが、こちらで紹介されている
LATEST_FILE="`ls -lt *.txt | head -n 1 | gawk '{print $9}'`"
この方法では、自分の環境ではなんと更新年が取得できてしまいました。
こちらの方法は、「ls -lt *.txt」で拡張子が.txtのファイルを更新日順に出力し、「head -n 1」で先頭行、つまり最新ファイルの行を取得し、「gawk '{print $9}'」でその行の9列目の文字列を取得しているというわけです。
つまり9列目にファイル名が存在するという決まりが無い限りはファイル名が取得できないのです。
自分の環境では9列目に更新年が表示されてしまい、ファイル名が取得できなかったのです。
10列目にファイル名が表示されるので、「gawk '{print $9}'」を「gawk '{print $10}'」に変えれば取得できるのですが、別の環境ではまた9列目に表示されるかもしれません。SEやPGといった職種は、できれば一度書いたプログラムに手を入れたくなく、別の環境に持って行っても最低限の設定を修正すれば同じ動きをしてほしいと常に考えるものだと思います。
9列目だろうが10列目だろうが、いずれにせよ行の最後にファイル名が出てくるということから、次に考えたのがこれ。
str=`ls -lt *.txt | head -1`;ary=(${str});cnt=${#ary[@]};echo ${ary[${cnt}-1]}
ちょっと長くてややこしいですが、;で改行すると多少わかりやすくなるかもしれません。
str=`ls -lt *.txt | head -1`
ary=(${str})
cnt=${#ary[@]}
echo ${ary[${cnt}-1]}
上から順に説明すると、
最新ファイルの行を変数strに格納。
変数strの中身を配列aryに格納。
配列aryの要素数を取得。
配列の最後の要素(=ファイル名)を出力。
という処理になります。これならls -ltで出力される内容が9列でも10列でも100列でも行の最後にファイル名が出る限り、ファイル名を取得可能です。ただファイル名1つ取るのに3つも変数を使っている。処理が多いとそれだけバグが入る可能性が多くなり、かえって面倒なので、最大限圧縮したのがこれ。
ary=(`ls -lt *.txt | head -1`);echo ${ary[${#ary[@]}-1]}
配列を作らなければならない以上、どうしても2段階になってしまいます。別に使えるんですが、なんとなくかっこ悪い。
「あ、そうだ、別に配列にしなくても、スペース区切りの最後の文字列を取得できればいいんだ」と考え、そして最終的にたどり着いた結果がこれ。
ls -lt *.txt | head -1 | sed -e "s/.* //g"
最新ファイルの行を取得し、sedコマンドで半角スペースを含む文字列を全部削除しています。
これで一応自分の望む結果が取得できたのですが、これだとファイル名に半角スペースが含まれていると取得に失敗しそうです。
要件に合わせて使いやすい方を使おうと思います。
【追記】
もっと楽に取得できる方法を教えてもらいました。
ls -1t *.txt | head -1
これだけでOK。1オプションがとても大事でした!lオプションで全情報出さなくても大丈夫ですね。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
主にAjaxなどのロード中に現れる、くるくる回るあれ。
あれってなんて呼ぶのかよくわからないので検索かけようにもかけにくくて探すのが面倒だったりします。
調べた結果、いろんな呼び方があるようです。
・くるくる(意外にも「Javascript くるくる」なんかで検索すると出てきたりします)
・スピナー
・処理中画像
・ローディング画像
・インジケータ
・プログレスインジケータ
などなど。どうも一定した呼び方は無いようです。
あれを「スピナー」と呼ぶのは知りませんでしたが、「インジケータ」や「プログレスインジケータ」などの名称はHTMLやWEBのロード以外にも使われるため、覚えておくと便利かもしれません。
結局あれの正式名称はなんでしょうね。
そして、いちいち画像を読み込まずにあの「くるくる」を再現できないものかとあれこれ探してみたところ、とても便利なものを見つけました。
spin.js | http://fgnass.github.io/spin.js/
その名の通り、スピンするあのくるくるをJavaScriptで作ってしまえるライブラリです。
使い方も非常に簡単で、ある程度JavaScriptの知識がある人ならすぐに実装できると思います。
spin.jsだけを動かすならjQueryのような他のライブラリも必要ないのですが、jQueryを使いたい人にはjQueryのプラグインも公開されています。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
あれってなんて呼ぶのかよくわからないので検索かけようにもかけにくくて探すのが面倒だったりします。
調べた結果、いろんな呼び方があるようです。
・くるくる(意外にも「Javascript くるくる」なんかで検索すると出てきたりします)
・スピナー
・処理中画像
・ローディング画像
・インジケータ
・プログレスインジケータ
などなど。どうも一定した呼び方は無いようです。
あれを「スピナー」と呼ぶのは知りませんでしたが、「インジケータ」や「プログレスインジケータ」などの名称はHTMLやWEBのロード以外にも使われるため、覚えておくと便利かもしれません。
結局あれの正式名称はなんでしょうね。
そして、いちいち画像を読み込まずにあの「くるくる」を再現できないものかとあれこれ探してみたところ、とても便利なものを見つけました。
spin.js | http://fgnass.github.io/spin.js/
その名の通り、スピンするあのくるくるをJavaScriptで作ってしまえるライブラリです。
使い方も非常に簡単で、ある程度JavaScriptの知識がある人ならすぐに実装できると思います。
spin.jsだけを動かすならjQueryのような他のライブラリも必要ないのですが、jQueryを使いたい人にはjQueryのプラグインも公開されています。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
JavaでCSVを読み込んでDBにINSERTするプログラムを作成する必要がありまして。
まずCSVを読み込むところ。
よくJavaのファイル読み込みの例に出されるように、FileInputStreamとInputStreamReaderとBufferedReaderを駆使して書いてもよかったんですが、せっかくだから便利なライブラリはないかと思って探していたところ、OpenCSVというのを見つけました。
このOpenCSV、CSVを読み込んで配列にしたりBeanに格納したりができるとのこと。凝ったことはできないようですが、今回はただ単に文字列を読み込んでDBにINSERTするだけですし、データの受け渡しにBeanをよく使う自分にはちょうどよかったので使ってみました。
このようなBeanを作成したとします。
public class HogeBean {
private String hoge;
private String hugu;
public String getHoge() {
return hoge;
}
public void setHoge(String hoge) {
this.hoge = hoge;
}
public String getHugu() {
return hugu;
}
public void setHugu(String hugu) {
this.hugu = hugu;
}
}
CSVを読み込んでこのBeanに格納するには、このように書きます。
詳しい使い方は他のサイトで調べてください。
File csvFile = new File("file.csv");
ColumnPositionMappingStrategy<HogeBean> strat = new ColumnPositionMappingStrategy<HogeBean>();
strat.setType(HogeBean.class);
String columns = new String[] {"hoge", "hugu"};
strat.setColumnMapping(columns);
CsvToBean<HogeBean> csvb = new CsvToBean<HogeBean>();
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
List<HogeBean> list = csvb.parse(strat, csvr);
これでListを操作してDBにINSERTするわけです。
しかし新たな問題が。
処理後CSVのファイル名を変更しなければならないという仕様でした。
Javaでファイル名を変更するには、File#renameTo(File)というメソッドを使用します。renameToメソッドを呼び出したFileオブジェクトのファイル名を引数のFileオブジェクトのファイル名に変更するという、そのものずばりなメソッドです。
File newFile = new File("error.csv");
csvFile.renameTo(newFile);
これでファイル名が変更できるはずなんですが…。いくらやっても変更されません。
調べてみると、renameToの挙動はOS任せであるとかいろいろ出てきて、別の方法を探す必要があるのかなと思っていたところ、あるサイトで「ファイルを開いている場合、変更できない」という記述を見ました。
いや、ローカルの開発環境で俺だけが使ってるし、誰も開いてないだろと思いましたが…重要なことに気が付きました。
結果として、ファイルが開かれていたためファイル名が変更できなかったのです。では、ファイルを開いていた犯人は誰か?
プログラム自身でした。
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
この行でストリームを開いていたんですが、クローズするのを忘れていました。
FileInputStreamとInputStreamReaderとBufferedReaderを駆使する旧来通りの書き方なら忘れることもなかったのですが、下手に使ったことがないライブラリに手を出してしまったために、すっかり「ストリームを開いたら必ずクローズする」というファイルIOの基本を忘れていました。
File csvFile = new File("file.csv");
ColumnPositionMappingStrategy<HogeBean> strat = new ColumnPositionMappingStrategy<HogeBean>();
strat.setType(HogeBean.class);
String columns = new String[] {"hoge", "hugu"};
strat.setColumnMapping(columns);
CsvToBean<HogeBean> csvb = new CsvToBean<HogeBean>();
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
List<HogeBean> list = csvb.parse(strat, csvr);
csvr.close(); //クローズする
File newFile = new File("error.csv");
csvFile.renameTo(newFile);
これで無事ファイル名が変更できました。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
まずCSVを読み込むところ。
よくJavaのファイル読み込みの例に出されるように、FileInputStreamとInputStreamReaderとBufferedReaderを駆使して書いてもよかったんですが、せっかくだから便利なライブラリはないかと思って探していたところ、OpenCSVというのを見つけました。
このOpenCSV、CSVを読み込んで配列にしたりBeanに格納したりができるとのこと。凝ったことはできないようですが、今回はただ単に文字列を読み込んでDBにINSERTするだけですし、データの受け渡しにBeanをよく使う自分にはちょうどよかったので使ってみました。
このようなBeanを作成したとします。
public class HogeBean {
private String hoge;
private String hugu;
public String getHoge() {
return hoge;
}
public void setHoge(String hoge) {
this.hoge = hoge;
}
public String getHugu() {
return hugu;
}
public void setHugu(String hugu) {
this.hugu = hugu;
}
}
CSVを読み込んでこのBeanに格納するには、このように書きます。
詳しい使い方は他のサイトで調べてください。
File csvFile = new File("file.csv");
ColumnPositionMappingStrategy<HogeBean> strat = new ColumnPositionMappingStrategy<HogeBean>();
strat.setType(HogeBean.class);
String columns = new String[] {"hoge", "hugu"};
strat.setColumnMapping(columns);
CsvToBean<HogeBean> csvb = new CsvToBean<HogeBean>();
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
List<HogeBean> list = csvb.parse(strat, csvr);
これでListを操作してDBにINSERTするわけです。
しかし新たな問題が。
処理後CSVのファイル名を変更しなければならないという仕様でした。
Javaでファイル名を変更するには、File#renameTo(File)というメソッドを使用します。renameToメソッドを呼び出したFileオブジェクトのファイル名を引数のFileオブジェクトのファイル名に変更するという、そのものずばりなメソッドです。
File newFile = new File("error.csv");
csvFile.renameTo(newFile);
これでファイル名が変更できるはずなんですが…。いくらやっても変更されません。
調べてみると、renameToの挙動はOS任せであるとかいろいろ出てきて、別の方法を探す必要があるのかなと思っていたところ、あるサイトで「ファイルを開いている場合、変更できない」という記述を見ました。
いや、ローカルの開発環境で俺だけが使ってるし、誰も開いてないだろと思いましたが…重要なことに気が付きました。
結果として、ファイルが開かれていたためファイル名が変更できなかったのです。では、ファイルを開いていた犯人は誰か?
プログラム自身でした。
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
この行でストリームを開いていたんですが、クローズするのを忘れていました。
FileInputStreamとInputStreamReaderとBufferedReaderを駆使する旧来通りの書き方なら忘れることもなかったのですが、下手に使ったことがないライブラリに手を出してしまったために、すっかり「ストリームを開いたら必ずクローズする」というファイルIOの基本を忘れていました。
File csvFile = new File("file.csv");
ColumnPositionMappingStrategy<HogeBean> strat = new ColumnPositionMappingStrategy<HogeBean>();
strat.setType(HogeBean.class);
String columns = new String[] {"hoge", "hugu"};
strat.setColumnMapping(columns);
CsvToBean<HogeBean> csvb = new CsvToBean<HogeBean>();
CSVReader csvr = new CSVReader(new InputStreamReader(new FileInputStream(csvFile), "UTF-8"));
List<HogeBean> list = csvb.parse(strat, csvr);
csvr.close(); //クローズする
File newFile = new File("error.csv");
csvFile.renameTo(newFile);
これで無事ファイル名が変更できました。
----------
ゆりまぐろ 百合男子予備軍によるゆるゆりブログ
現在の開発では、これまでの旧態依然とした開発ではなく、ライブラリ管理やビルドをMavenで行い、データベース周りはMyBatisを使用し、DIコンテナとしてGuiceを使用し、SVNでソース管理をしておいてJenkinsがSVNを監視して変更があったらビルドしてJUnitでテストを行う。
・・・と、割と(今更感が否めませんが)近代化したことをやっております。
いやー、MavenとMyBatisっていいですねー。Mavenならいちいちネット上からライブラリを探して落としてきてlibフォルダ配下に置いてパスを通してってやる必要がないんですから。MyBatisも、ソースにはメソッドだけ宣言しておいて、実装はXMLでできます。SQLをXMLで外出しできるのが非常にありがたいですね。なんで今までSQLをソースに直書きしてたんだろう?っていうか今まで長いことJavaとDBを触ってきて知らなかったってのが情けない。
しかしちょっと初めてなこと尽くしで苦労したことも多いです。
特に苦労したのがローカルでJUnitを起動して成功したのでSVNに上げたところ、Jenkinsではテストが失敗するという謎な現象。
いくらJenkinsのテストのログを見ても、「そうは言ってもローカルではちゃんと設定してあるよ」ということばかり。
特に意味不明だったのが、「Mapped Statements collection does not contain value for クラス名」というエラー。
このエラー、ちょっと検索すると、MyBatisの設定の問題で、先ほど挙げたメソッドを宣言したソースと実装のXMLがかみ合ってない時に出るエラーのようです。
たとえば「HogeMapper.java」という名前でメソッドを宣言するソースを作った場合、XMLは「HogeMapper.xml」という名前にして、きちっとXML内のnamespaceにHogeMapperを完全修飾名で宣言しなければなりませんが、これらが上手くかみ合っていないと前述のエラーになってしまいます。「中身がねーよ」って言われてるんですね。
しかしもちろんローカルでは上手くいっているわけですから、そんなミスはあり得ません。
なんでかなーとずっと悩んでいたのですが、ふとJenkins内のclassファイル出力フォルダを見ると、HogeMapper.classはあるのにHogeMapper.xmlがありませんでした。
そうなのです、自分はHogeMapper.javaとHogeMapper.xmlを同じフォルダに入れていたのです。
Eclipseで自動的にビルドを行うと、ソースフォルダに置いてある*.java以外のファイルも勝手にclassファイル出力先にコピーしてくれますが、Mavenでビルドを行うとそうはいかないのです。
たぶん設定で変えられると思うのですが、Mavenではリソースの配置場所が最初からあらかじめ決められています。
javaのソースファイルはsrc/main/java配下に、それ以外のリソースはsrc/main/resources配下に。他にもいろいろ決まっているので、気になる方はここ(
)を参照してください。
つまり、classファイル出力先でjavaとxmlを一緒にしたい場合、
src/main/java配下にHogeMapper.javaを作成したら、
src/main/resources配下にHogeMapper.xmlを置く必要があったわけでした。
気が付くまで結構時間がかかりました。だってなかなか検索しても出てこないんだもの…。
たぶん常識だろと思う人もいるんでしょうが、自分にとっては新しいことだったので備忘的に記事にしました。
http://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html
・・・と、割と(今更感が否めませんが)近代化したことをやっております。
いやー、MavenとMyBatisっていいですねー。Mavenならいちいちネット上からライブラリを探して落としてきてlibフォルダ配下に置いてパスを通してってやる必要がないんですから。MyBatisも、ソースにはメソッドだけ宣言しておいて、実装はXMLでできます。SQLをXMLで外出しできるのが非常にありがたいですね。なんで今までSQLをソースに直書きしてたんだろう?っていうか今まで長いことJavaとDBを触ってきて知らなかったってのが情けない。
しかしちょっと初めてなこと尽くしで苦労したことも多いです。
特に苦労したのがローカルでJUnitを起動して成功したのでSVNに上げたところ、Jenkinsではテストが失敗するという謎な現象。
いくらJenkinsのテストのログを見ても、「そうは言ってもローカルではちゃんと設定してあるよ」ということばかり。
特に意味不明だったのが、「Mapped Statements collection does not contain value for クラス名」というエラー。
このエラー、ちょっと検索すると、MyBatisの設定の問題で、先ほど挙げたメソッドを宣言したソースと実装のXMLがかみ合ってない時に出るエラーのようです。
たとえば「HogeMapper.java」という名前でメソッドを宣言するソースを作った場合、XMLは「HogeMapper.xml」という名前にして、きちっとXML内のnamespaceにHogeMapperを完全修飾名で宣言しなければなりませんが、これらが上手くかみ合っていないと前述のエラーになってしまいます。「中身がねーよ」って言われてるんですね。
しかしもちろんローカルでは上手くいっているわけですから、そんなミスはあり得ません。
なんでかなーとずっと悩んでいたのですが、ふとJenkins内のclassファイル出力フォルダを見ると、HogeMapper.classはあるのにHogeMapper.xmlがありませんでした。
そうなのです、自分はHogeMapper.javaとHogeMapper.xmlを同じフォルダに入れていたのです。
Eclipseで自動的にビルドを行うと、ソースフォルダに置いてある*.java以外のファイルも勝手にclassファイル出力先にコピーしてくれますが、Mavenでビルドを行うとそうはいかないのです。
たぶん設定で変えられると思うのですが、Mavenではリソースの配置場所が最初からあらかじめ決められています。
javaのソースファイルはsrc/main/java配下に、それ以外のリソースはsrc/main/resources配下に。他にもいろいろ決まっているので、気になる方はここ(
)を参照してください。
つまり、classファイル出力先でjavaとxmlを一緒にしたい場合、
src/main/java配下にHogeMapper.javaを作成したら、
src/main/resources配下にHogeMapper.xmlを置く必要があったわけでした。
気が付くまで結構時間がかかりました。だってなかなか検索しても出てこないんだもの…。
たぶん常識だろと思う人もいるんでしょうが、自分にとっては新しいことだったので備忘的に記事にしました。
http://maven.apache.org/guides/introduction/introduction-to-the-standard-directory-layout.html
WEBサイトの画面がフレームで分割され、フレームのどれが固定のメニューで、クリックするとコンテンツが別の分割されたフレームに表示される。という形式のWEBサイトはよく見かけます。それらはだいたい<frame>タグを使って実現しています。
しかし、HTML5では<frame>タグが廃止されましたし、HTML4でも実は<frame>タグは非推奨のタグでした。まあ、実際には非推奨でもほとんどのブラウザが対応しているので表示されますし、HTML5になったからと言ってブラウザがHTML5以下のバージョンが表示できなくなるわけではないので実害があるわけではないのですが、なんとなく<frame>で画面を区切るのがダサい気がしたので、<frame>タグを使用せずに画面をフレーム分割してみました。その時のメモ。
まず、CSSで全てのmarginとpaddingをクリアします。これは擬似フレームのためというよりも、レイアウトを考えやすくするため。<div>タグや<p>タグなどのボックス要素は勝手に上下に間隔が開いたりしますが、それを解除するためです。
その際、bodyタグのoverflowはhiddenにします。
「left」はmarginとpaddingを0にします。widthはサイトの設計に従い任意の幅でOKですが、とりあえず200pxにします。
positionをabsoluteにし、topを0、leftを0にします。これ大事です。「left」の位置決めの設定なのですが、これでブラウザの左上にきっちり表示されます。
heightを100%に設定します。これも大事。ブラウザ開いた時にブラウザがどんな大きさでも縦がきっちり表示されます。
CSSの設定はこんな感じになります。
marginはちょっと特殊で、上下右は0にしますが、左は「left」のwidthと同じ値を指定します。
「left」と同じくheightを100%にします。
そして「right」にはコンテンツを表示する時にスクロールするため、overflowをautoにします。
こんな感じの設定になります。
ここからさらに中身を記述していくわけですが・・・書くのが面倒になってきたので次回に続けます。Firefoxのリッチテキストエディタの限定を解除しないと・・・。
しかし、HTML5では<frame>タグが廃止されましたし、HTML4でも実は<frame>タグは非推奨のタグでした。まあ、実際には非推奨でもほとんどのブラウザが対応しているので表示されますし、HTML5になったからと言ってブラウザがHTML5以下のバージョンが表示できなくなるわけではないので実害があるわけではないのですが、なんとなく<frame>で画面を区切るのがダサい気がしたので、<frame>タグを使用せずに画面をフレーム分割してみました。その時のメモ。
まず、CSSで全てのmarginとpaddingをクリアします。これは擬似フレームのためというよりも、レイアウトを考えやすくするため。<div>タグや<p>タグなどのボックス要素は勝手に上下に間隔が開いたりしますが、それを解除するためです。
その際、bodyタグのoverflowはhiddenにします。
* {margin: 0; padding: 0;}
body {margin: 0; padding: 0; overflow: hidden;}
んで、左右2分割にする場合はdivタグでボックスを2つ作り、それぞれにIDを振ります。ここでは「left」と「right」にしておきます。body {margin: 0; padding: 0; overflow: hidden;}
「left」はmarginとpaddingを0にします。widthはサイトの設計に従い任意の幅でOKですが、とりあえず200pxにします。
positionをabsoluteにし、topを0、leftを0にします。これ大事です。「left」の位置決めの設定なのですが、これでブラウザの左上にきっちり表示されます。
heightを100%に設定します。これも大事。ブラウザ開いた時にブラウザがどんな大きさでも縦がきっちり表示されます。
CSSの設定はこんな感じになります。
#left {
margin: 0px;
padding: 0px;
width: 200px;
position: absolute;
top: 0;
left: 0;
height: 100%;
}
「right」はpaddingを0にします。widthは指定しません。margin: 0px;
padding: 0px;
width: 200px;
position: absolute;
top: 0;
left: 0;
height: 100%;
}
marginはちょっと特殊で、上下右は0にしますが、左は「left」のwidthと同じ値を指定します。
「left」と同じくheightを100%にします。
そして「right」にはコンテンツを表示する時にスクロールするため、overflowをautoにします。
こんな感じの設定になります。
#right {
padding: 0px;
margin: 0 0 0 200px;
height: 100%;
overflow: auto;
}
これで一応左右2分割のベースができます。padding: 0px;
margin: 0 0 0 200px;
height: 100%;
overflow: auto;
}
ここからさらに中身を記述していくわけですが・・・書くのが面倒になってきたので次回に続けます。Firefoxのリッチテキストエディタの限定を解除しないと・・・。