あふで peco を使う
Windows ファイラーの あふw で、peco を使ってフォルダ履歴をいい具合に使いたい。業務上、あっちこっちの Windows Server を UNC パスによりあふで見まくってて、よく使うフォルダ登録だけではきつくなってきたので、履歴からさくっとアクセスできるといいなぁと。
ということで、いろいろやってたら、bat 書いて、perl 書いて、jscript 書いての実装となってしまったが、非常に実用的になった。migemogrep も併せると日本語フォルダにも対応できて最高。
以下のとおり実装した。それぞれのスクリプトは C:\local\bin\afxw\script に置く。
afxw-peco.bat
このバッチはあふから実行するもの(Shift-h に割り当て)で、あふのフォルダ履歴を peco に食わせて、絞り込んだ結果を js を介してあふに渡す。js に渡す前に iconv で cp932 に変換する。Windows バッチの書き方がいまいちよくわからん…。
@echo off cd /d C:\local\bin\afxw\script for /f "delims=" %%i in ('perl afxw-hist.pl ^| peco ^| iconv -f UTF-8 -t CP932') do ( cscript afxw-cd.js "%%i" break )
afxw-hist.pl
あふのオートメーションサーバ機能でフォルダ履歴を取ってきて、出力する。さくっと Perl で。
#!/usr/bin/env perl use 5.010; use strict; use warnings; use utf8; use Encode; use Win32::OLE; my $afxw = Win32::OLE->new('afxw.obj'); my (@dirs, %h); for my $window (0, 1) { my $count = $afxw->HisDirCount($window); for (my $i = 0; $i < $count; $i++) { my $dir = decode('cp932', $afxw->HisDir($window, $i)); push @dirs, $dir unless $h{$dir}; $h{$dir} = 1; } } say encode('utf8', $_) for @dirs;
afxw-cd.js
指定したフォルダをあふのオートメーションサーバ機能で開く。
var args = WScript.Arguments; var dir = args(0); var afxw = WScript.CreateObject("afxw.obj"); var cd = '&EXCD -P"' + dir + '"'; afxw.Exec(cd);
afxw.ini
フォルダ履歴はデフォルト 36 個なので、増やす。あまり多すぎると重くなるのかしら。
HISMAX=4096
afxw.key
あふで Shift-h すると、peco (コマンドプロンプト) が起動するように。
K0026="1072C:\local\bin\afxw\script\afxw-peco.bat"
Vimperator で読み込む設定ファイルを分岐する
.vimperatorrc を git で管理してて、家の Mac と職場の Windows で設定ファイルを共用してる。
で、C-k で Emacs よろしくなことがしたいため、以下を設定したところ、Mac での挙動がおかしくなるので、これを Windows のみに適用したい。
inoremap <C-k> <S-End><C-x> cnoremap <C-k> <S-End><C-x>
調べたら services.get("environment") で環境変数が取れるようなので、次のようにすれば Windows のときのみそれ用の設定ファイルを読み込むができ、それに上記の設定を書けばいい感じになった。
javascript <<EOM if ( services.get("environment").get("OS").match(/Windows/i) ) { io.source("~/vimp/_win_vimperatorrc", false); } EOM
追記
コメントで config.features を教えていただいた。で、ソースを読んでたら
has: function (feature) config.features.indexOf(feature) >= 0
とあったので、次のようにするとスマート!
javascript <<EOM if ( liberator.has("Windows") ) { io.source("~/vimp/_win_vimperatorrc", false); } EOM
ビジュアルモードで選択した要素の最後にキャレットを移動する
ブラウザで長い文章を読むとき、読んでるパラグラフなどを選択しながら読む癖があって、vimperator だと c v hjkl でやるわけだが、ずっと選択しっぱなしがイヤなので、ほどほどのところで Esc して選択解除したりする。するとキャレットが選択要素の最初になって、どこまで読んだかわからなくなりキーッとなって、これが相当なストレスであった。
で、なにげに caret-hint.js を読んでたら、こんな感じでやればいいのかとヒントを得た(まさに caret-hint !)ので、次のとおり書いてみた。というか、ほとんど caret-hint.js のパクリだけど。これで c v hjkl で選択していって、適当なところで n すると選択範囲の最後にキャレットが移動して、捗るわー。
mappings.addUserMap( [modes.VISUAL], ['n'], 'Move selection tail', function () { let win = new XPCNativeWrapper(window.content.window); let s = win.getSelection(); if (s.rangeCount <= 0) return false; let f = [s.focusNode, s.focusOffset]; s.collapse.apply(s, f); } );
さらに、次を定義すると m したら最後にキャレットが移動した後ビジュアルモードを終えるようになり、n と使い分けるとなかなかいい感じ。
vmap m n<ESC><CR>
さくらVPS 2G で VirtualBox による Windows 環境の構築
これまで「さくら VPS 512」を2つ契約してて Debian と Windows をそれぞれ使っていたが、2012-03-29 にさくら VPS がリニューアルして、メモリもハードディスクも増量し、2G プランも登場したことから、仮想化でやれば 2G プラン1本でいけるんじゃないかと思い、移行してみた。
さくら VPS の OS は Debian とし、それに仮想化ソフトウェア VirtualBox を入れ、ゲスト OS として Windows XP x86 を構築する。さくら VPS は KVM なので、Windows は仮想化の仮想化での動作となる。
ちなみに、仮想化された Windows クライアントにアクセスするには Windows SA または Windows VDA のライセンスが必要となる。
Debian のインストール
まずは普通に Debian squeeze amd64 をインストールする(i386 のほうがよかったかもしれないが、どうなんだろ)。VirtualBox を GUI でサクッとやるため、インストール時にデスクトップ環境も入れる。
ssh と iptables
OS インストール後、なにはともあれ ~/.ssh/authorized_keys に公開鍵を登録し ssh と iptables を設定する。
ssh のポート番号を 25622 に変更し、公開鍵認証のみとする。
$ sudo vi /etc/ssh/sshd_config Port 25622 Protocol 2 PermitRootLogin no RSAAuthentication no PubkeyAuthentication yes PermitEmptyPasswords no ChallengeResponseAuthentication no PasswordAuthentication no UsePAM no $ sudo /etc/init.d/ssh restart
/etc/network/if-pre-up.d に iptables のスクリプトを置き、ssh だけ開ける。
$ sudo vi /etc/network/if-pre-up.d/firewall #!/bin/sh IPTABLES='/sbin/iptables' # Remove All Rule $IPTABLES -F $IPTABLES -Z $IPTABLES -X # Drop ALL $IPTABLES -P INPUT DROP $IPTABLES -P FORWARD DROP $IPTABLES -P OUTPUT ACCEPT # Accept Loopback $IPTABLES -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT $IPTABLES -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT # PING $IPTABLES -A INPUT -p icmp --icmp-type 0 -j ACCEPT $IPTABLES -A INPUT -p icmp --icmp-type 8 -j ACCEPT # SSH $IPTABLES -A INPUT -p tcp --dport 25622 -j ACCEPT $ sudo chmod +x /etc/network/if-pre-up.d/firewall $ sudo /etc/network/if-pre-up.d/firewall
VirtualBox 4.1 のインストール
Oracle VM VirtualBox から deb パッケージを落としてきて、dpkg でインストールする。依存関係で怒られたので、apt-get -f upgrade する。
$ wget http://download.virtualbox.org/virtualbox/4.1.12/virtualbox-4.1_4.1.12-77245~Debian~squeeze_amd64.deb $ sudo dpkg -i virtualbox-4.1_4.1.12-77245\~Debian\~squeeze_amd64.deb $ sudo apt-get -f upgrade $ sudo usermod -a -G vboxusers username
VirtualBox への Windows x86 のインストール
GNOME デスクトップの [アプリケーション]-[システムツール]-[Oracle VM VirtualBox] から VirtualBox をいぢるわけだが、X の解像度が 800x600 でつらい。xorg.conf を作ってゴニョるも、うまく解像度を上げることができない…。VirtualBox の設定さえ終えたら X は殺すのでまぁいいかと深追いせず進める。下図のとおり VirtualBox 画面右下の Cancel, OK が隠れてどっちがどっちかわからんで困ったけども…。
で、ウィザードで新規仮想マシンを作り、以下のような構成とした(ほとんどデフォルト)。
OS | Windows XP x86 |
---|---|
メモリ | 768MB |
ハードディスク | 30GB, VDI, Dynamically allocated |
チップセット | PIIX3, IO APIC オフ, EFI オフ |
プロセッサ数 | 1, PAE/NX オフ |
ビデオメモリ | 16MB, 3D/2D オフ |
ストレージ | PIIX4 |
ネットワーク | NAT, ホストオンリー |
プロセッサ数は2個にしたかったが、KVM 上のため仮想化支援機能が有効でないので、残念ながら1個の割り当てにしかできない。ネットワークはグローバル IP アドレスが1つしかもらえないので当然 NAT になるが、ホストからゲストにアクセスしたい (後述) ので、ホストオンリーの NIC を追加する (DHCP ではなくstatic で設定した)。
あとは iso からブートして普通に Windows をインストールすれば出来上がり。Windows のインストール後、[デバイス]-[Guest Additions のインストール] を実行し、ドライバ類をインストールする。
リモートデスクトップ
外部から Windows を操作するため、リモートデスクトップ機能を使いたい。VirtualBox には VRDP 機能 (リモートディスプレイ) があるが、ゲストは Windows なので直接 MS-RDP でやることにする。試してないけど、たぶんそのほうが性能的にもいい気がする。
また、ゲストは NAT なので、外部から直接アクセスするには VirtualBox のポートフォワーディング機能を使うのが一般的だろうが、せっかく Debian の裏に隠れているので、ssh トンネリングでリモートデスクトップ接続することにした。通信も暗号化されるし。といった理由で、ホスト (Debian) からゲスト (Windows) に通信できるようにするため、前述のとおり、ゲストにホストオンリーのネットワークを追加した。
ゲストのホストオンリー NIC の IP アドレスが 192.168.56.101 のとき、PuTTY の場合、次のように設定すれば、ssh 接続後 mstsc /v localhost:33891 で Windows にリモートデスクトップ接続できるようになる。
L33891 192.168.56.101:3389
Mac とかの場合 ~/.ssh/config に次のように書くと楽ちん。
Host debian HostName example.jp User username Port 25622 LocalForward 33891 192.168.56.101:3389 IdentityFile ~/.ssh/id_rsa
Debian の不要なサービスの停止
初期設定さえ済んでしまえば Debian のウィンドウシステムは不要なので gdm3 を停止する。併せて、以下の不要なサービスを止める。sysv-rc-conf で止めるとよい。
$ sudo apt-get install sysv-rc-conf $ sudo sysv-rc-conf
コマンドによる VirtualBox の操作
VirtualBox はコマンドが充実している。以下のコマンドで仮想マシンの起動・停止ができる。xp のところは仮想マシンの vmid を。
起動
$ VBoxManage startvm xp --type headless
停止 (というか状態保存して停止)
$ VBoxManage controlvm xp savestate
起動している仮想マシンの表示
$ VBoxManage list runningvms
自動起動と自動停止
上記のコマンドを叩くスクリプトを作って /etc/init.d に置きランレベル2に登録すれば、ホストの起動時にゲストも自動起動できる。スクリプトは http://www.glump.net/howto/virtualbox_as_a_service#create_the_initd_script あたりがいいかも。これを使う場合、Required-Start の vboxnet を削除して、vboxballoonctrl-service を追加する必要がある。
$ sudo insserv virtualbox-xp
なお、上記スクリプトによりホスト停止時に savestate されるが、以下のとおり /etc/default/virtualbox を作っておけば、/etc/init.d/vboxdrv によりホストを落としたときに savestate してくれる。
$ sudo vi /etc/default/virtualbox SHUTDOWN_USERS="username" SHUTDOWN=savestate
ベンチマーク
参考までに、これまでのネイティブ(?)な on KVM と今回の on VirtualBox on KVM でのベンチマークを取ってみた。
on KVM
on VirtualBox on KVM
数値で示されているとおり、さすがに VirtualBox を挟んでいるため半分以下の性能になっており、実際も VPS ネイティブな環境に比べると少々もっさり感があるが、ガリガリ使うわけではないし、主な用途である TARGET (競馬ソフト) の操作や JRA-VAN DataLab. データの PostgreSQL (on Debian) への登録など、特に遅延することはなく動作に問題はない。
Read It Later からはてブに登録する
多くの iPhone アプリでは、あとで読む系は Read It Later (以下 RIL) が多いので、RIL に流しているが、通常ははてブを使っているため、RIL に登録した URL をはてブに集約したい。というか、仕事から現実逃避したいので、コード書きたい。
というわけで、以下のようなスクリプトを作り、cron で回して、昨日以降に RIL に登録した URL を自動的にはてブに登録するようにしたった。なお、RIL の API にアクセスするため、API Key を取得する必要がある。
#!/usr/bin/perl # Read It Later に昨日以降登録されたものを # Hatena Bookmark に登録する use 5.010; use strict; use warnings; use utf8; use Data::Dumper; use JSON; use DateTime; use DateTime::TimeZone; use LWP::Simple; use LWP::UserAgent; use WebService::Hatena::Bookmark::Lite; my $ril_url = 'https://readitlaterlist.com/v2/get?username=%s&password=%s&apikey=%s&since=%s'; my $ril_user = 'user'; my $ril_pass = 'password'; my $ril_key = 'apikey'; my $hatena_user = 'user'; my $hatena_pass = 'password'; my $tz = DateTime::TimeZone->new(name => 'Asia/Tokyo'); # 昨日のエポック秒 my $yesterday = DateTime->today(time_zone => $tz)->subtract(days => 1); my $ril_since = $yesterday->epoch(); # RIL から JSON を取得する my $ril = sprintf($ril_url, $ril_user, $ril_pass, $ril_key, $ril_since); my $json = get($ril); # こんな JSON が返ってくる #my $json = '{"status":1,"list":{"74006":{"item_id":"74006","title":"Google","url":"http:\/\/www.google.co.jp\/","time_updated":"1325814579","time_added":"1325814579","state":"0"}},"since":1325737324,"complete":0}'; my $data = decode_json($json); my $list = $data->{list}; my $bookmark = WebService::Hatena::Bookmark::Lite->new( username => $hatena_user, password => $hatena_pass, ); # time_added でソート for my $key ( sort { $list->{$a}{time_added} <=> $list->{$b}{time_added} } keys %$list ) { my $url = $list->{$key}{url}; $url = expand_uri($url) if $url =~ m|^http://t\.co/|; # t.co は展開する # 登録された日時をコメントにする my $dt = DateTime->from_epoch( epoch => $list->{$key}{time_added}, time_zone => $tz, ); my $comment = $dt->strftime('%Y-%m-%d %H:%M:%S'); $bookmark->add( url => $url, comment => $comment ); say $comment; say $url; } # 短縮 URL を展開する sub expand_uri { my $uri = shift; my $ua = LWP::UserAgent->new; my $res = $ua->head($uri); return $res->request->uri; }
フォロワーの差分(OAuth 版)
http://d.hatena.ne.jp/chabom/20100818/1282092637 のコードはBasic 認証でもう使えないので、OAuth 対応した。
利用するには、Consumer key, Consumer secret, Access Token, Access Token Secret は Twitter Developer Platform — Twitter Developers でアプリを登録して取得する必要があります。
signature 作るところで、16 進表記は大文字にしないといけないのに気づかずにハマッた。System.Web.HttpUtility.UrlEncode は小文字で出力される。バカチン。
Hexadecimal characters in encodings MUST be upper case
http://oauth.net/core/1.0/#encoding_parameters
$API_URL = "https://api.twitter.com/1/statuses/followers.xml" $API_METHOD = "GET" $OLD_FILE = "followers_old.txt" # System.Web.HttpUtility.UrlEncode だと 16 進数が小文字になってダメなので function Encode-URL($s){ ($s.ToCharArray() | foreach { if ($_ -match "[a-z]|[-,_,.,~,)]|[0-9]") { $_ } else { "{0}{1:X}" -f "%", [System.Text.Encoding]::ASCII.GetBytes($_)[0] } }) -join "" } function Create-OAuthHeader { param ( [string]$url, [string]$method ) $CONSUMER_KEY = "Consumer key" $CONSUMER_SECRET = "Consumer secret" $TOKEN = "Access Token" $TOKEN_SECRET = "Access Token Secret" # unix-time $timestamp = [int](((Get-Date) - [DateTime]"1970/01/01 09:00:00").TotalSeconds) $rand = New-Object System.Random $nonce = $rand.next(123400, 9999999).ToString() $params = @("oauth_consumer_key=$CONSUMER_KEY", "oauth_nonce=$nonce", "oauth_signature_method=HMAC-SHA1", "oauth_timestamp=$timestamp", "oauth_token=$TOKEN", "oauth_version=1.0") | sort # signature を作る $base = @( $method, (Encode-URL $url), (Encode-URL ($params -join "&")) ) $base = $base -join "&" $hmac = New-Object System.Security.Cryptography.HMACSHA1 $hmac.Key = [System.Text.Encoding]::ASCII.GetBytes($CONSUMER_SECRET + "&" + $TOKEN_SECRET) $hash = $hmac.ComputeHash( [System.Text.Encoding]::ASCII.GetBytes($base) ) $signature = Encode-URL( [System.Convert]::ToBase64String($hash) ) $params += "oauth_signature=$signature" return $params -join "," } $wc = New-Object System.Net.WebClient $users = @() $cursor = -1 while ($cursor -ne 0) { $header = Create-OAuthHeader $API_URL $API_METHOD $wc.Headers.Set("Authorization", "OAuth $header") $xml = [xml] $wc.DownloadString($API_URL + "?cursor=$cursor") $cursor = $xml.users_list.next_cursor # $users に 現在のfollowersを入れる $xml.users_list.users.user | foreach { $users += $_.screen_name } } $wc.Dispose() # diff Compare-Object $(Get-Content $OLD_FILE) $users -SyncWindow 100 # 現在のfollowersをold.txtに出力する Set-Content -Path $OLD_FILE -Value $users "current followers: " + $users.length
フォロワーの差分(Basic認証版)
このコードは、TwitterのBASIC認証廃止に伴い利用できません。
$URL = "http://twitter.com/statuses/followers.xml?cursor=" $USER = "username" $PASSWD = "password" $OLD_FILE = "followers_old.txt" $nc = New-Object System.Net.NetworkCredential($USER, $PASSWD) $wc = New-Object System.Net.WebClient $wc.Credentials = $nc $users = @() $cursor = -1 while ($cursor -ne 0) { $xml = [xml] $wc.DownloadString($URL + $cursor) $cursor = $xml.users_list.next_cursor # $users に 現在のfollowersを入れる $xml.users_list.users.user | foreach { $users += $_.screen_name } } $wc.Dispose() # diff Compare-Object $(Get-Content $OLD_FILE) $users -SyncWindow 100 # 現在のfollowersをold.txtに出力する Set-Content -Path $OLD_FILE -Value $users "current followers: " + $users.length
ってか、結局、Basic認証っていつまで使えるのだろう? こういうのもいちいちOAuthせにゃならんのか?