Mac で Vim とクリップボードを共有

Vim で OSのクリップボードを共有する時は、.vimrcに 「set clipboard=unnamed」と書けば良い。

set clipboard=unnamed

これで vim上でヤンクしたものとかが、Command + V とかで張りつけできたりする。

ところが、ある日ふと家の Mac OS X(Lion)で出来てない事にきづいた。vimrcを見直してみたけど、特に問題なさそうというか、会社でも同じvimrcの設定使ってるのに旨くいってるし。。。

良くわからないのでググったら、答えが見つかった。

なんとデフォルトでインストールされてるvimだと 「-clipboard」て書いてあって、つまり無効。F○○K!!

~$ /usr/bin/vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Jun 24 2011 23:09:22)
Compiled by root@apple.com
Normal version without GUI.  Features included (+) or not (-):
-arabic +autocmd -balloon_eval -browse +builtin_terms +byte_offset +cindent 
-clientserver -clipboard +cmdline_compl +cmdline_hist +cmdline_info +comments
..
..

vimのインストール

というわけで vimのインストールしますお。 macportsとか使い方忘れたので、情弱な僕はhomebrewでインストール。。。。と探したら無い。。。。どうやら提供されていないようだ。。。野良Formulaという選択肢も考えたけど、なんかみんなつまづいてるから危険

~$ brew search vim
macvim              vimeo-downloader    vimpager            vimpc

という訳で、素直にソースからインストール。最近のvimMercurialリポジトリを公開しているので、それを利用するのが多分一番手っ取り早い。パッチもあたってる状態なので、最新のものがインストールできる。

hg clone https://vim.googlecode.com/hg/ vim
cd vim
hg update vim73
make clean
./configure --prefix=/usr/local --enable-multibyte --enable-xim --enable-fontset --enable-rubyinterp --enable-perlinterp --enable-pythoninterp --with-features=huge --disable-selinux
make
make install

これで大体、僕が必要そうなものはインストールされる。結果はこんな感じ

$ vim --version
VIM - Vi IMproved 7.3 (2010 Aug 15, compiled Feb 16 2012 21:56:06)
MacOS X (unix) version
Compiled by tell_k@tell-k-mackbookair.local
Huge version without GUI.  Features included (+) or not (-):
+arabic +autocmd -balloon_eval -browse ++builtin_terms +byte_offset +cindent 
-clientserver +clipboard +cmdline_compl +cmdline_hist +cmdline_info +comments
..
..

ちゃんと「+clipboard」になってますね。めでたし。めでたし。 良かったね > おれ

Mac で mkdpreview-vim を使って reST のプレビューを見る

mkdpreview-vim は Markdown記法で書かれたテキストを書きながら、別ウィンドウ上でプレビューも一緒に確認できる便利プラグイン。

Markdown以外に、reST と textile にも対応している。
これを利用してMacOS X 上で reSTructuredTextのプレビューを見てみる。

以下 プレビューを表示するまで。

環境

  • MacOS X

pyqtのインストール

  • 時間かかるんで最初にインストールしておく。
  • 面倒なのでhomebrewでまるっとインストール
$ brew install pyqt
  • Qt と sit と PyQT4 を一気にインストールするから、多分 20~30分くらい時間かかる。
  • インストールが完了したら、PyQTのある場所にパスを通す。
$ export PYTHONPATH=/usr/local/lib/python

Sphinxのインストール

  • reSTをプレビューするためには、docutilsが必要です。Sphinxはdocutilsに依存してるのでpipでインストールすると、Sphinxとdocutilsが一緒にインストールされる。
  • なんでわざわざSphinxまで入れるのかというと、Sphinxがデフォルトで持ってるディレクティブを利用したいから。
$ pip install Sphinx

vimプラグインのインストール

  • vundleを利用して、必要なプラグインをインストール。vundleが何か分からない人は下記を参照してください。
  • .vimrc に下記のように書いて、BundleInstallを実行する。
Bundle 'mattn/mkdpreview-vim'
Bundle 'mattn/webapi-vim'
  • インストールが終わったらmkdpreview-vimに同梱されてるPythonスクリプトに実行権限を与えておく。
$ chmod u+x ~/.vim/bundle/mkdpreview-vim/static/mkdpreview.py

mkdpreview.vimをいじる。

  • 「~/.vim/bundle/mkdpreview-vim/plugin/mkdpreview.vim」を開いて5行目あたりを下記のように編集する。
 #変更前
  \ "data" : join(getline(1, line('$')), "\n")

 #変更後
  \ "data" : join(getline(1, line('$')), "\n"), 
  \ "type" : 'rst'
  • 注意: 多分そのうち 正式にtypeを指定する方法は変わると思います。

追記(2012/02/13)

  • 最新版では type値に関しては、filetypeを自動で読み取って勝手に指定しくれるようになりました。上記修正は必要ありません :)
  • 下記のようになってました。
function! s:update_preview() 
  let ret = http#post('http://localhost:8081/', {
  \ "data" : join(getline(1, line('$')), "\n"),
  \ "type" : &filetype
  \})
  echo ret.content
endfunction

さぁプレビュー

  • こんなrstファイルを用意してvimで開く
.. contents:: 目次 

The Road of the Drillbits
===============================


第1章 どりるびっつの仲間
---------------------------------

.. code-block:: python

  print "The Fellowship of the Drillbits"


第2章 二つのどりるびっつ
---------------------------------

.. code-block:: python

  print "The Two Drillbits"


第3章 どりるびっつの帰還
---------------------------------

.. code-block:: python

  print "The Return of the Drillbits"
  • vimを開いて下記コマンドを実行
:MkdPreview! 
  • するとおもむろにウィンドウが立ち上がりプレビューがこんな感じでみれる!

f:id:tell-k:20120210194049p:plain

  • 以後は、「:w」で保存すれば勝手にプレビューが更新される。うまく更新されなかったら「:MkdPreview」と打つと良い。

code-blockディレクティブとか使いたい。

  • 上の画像では、「.. code-block::」の所で「Unknown directive type "code-block".」という感じで、そんなディレクティブねーよとおこられる
  • これはそもそも「.. code-block::」が、docutilsのディレクティブではなく、Sphinxに付属するディレクティブだから。Sphixはインストール済みなので、Sphinx付属のディレクティブが利くようにまたちょっといじる。

「~/.vim/bundle/mkdpreview-vim/static/plugin/mod_rst.py」というファイルを開いて下記のよう2行追加する。

  from docutils.core import publish_parts
+ from sphinx.directives.other import * 
+ from sphinx.directives.code import *
  • これでもう一度、ファイルを開き直して、「MkdPreview!」を実行すると、無事にエラーが無くなる

f:id:tell-k:20120210195550p:plain

以上おわり。

余談

  • 「.. code-block::」とか要らないという人は「mod_rst.py」を書き換えないで、且つSphinxをインストールせずに「pip install docutils」とすれば良いでしょう。
  • mattnさんが仕事はやすぎで「rstpreview-vim」作ったのにお蔵入りした。
  • PyQT 初めていれてみたけど、おもしろそうな感じ
  • もはや Markdown Preview ではないとか小さい事を気にしてはいけない
  • はてなブログ いつのまにか カテゴリ機能みたいなの動いてる

bpmappers 便利!

今日は bpmappers というライブラリが、すごく便利で感動したという事を書く

良くJSONを返すようなAPIを作るときに、オブジェクトの必要な部分だけを、辞書に変換してからJSONにするなんて事をすると思う。オブジェクトのプロパティが、数値やテキストなどリテラルなものであれば良いが、別のオブジェクトだったりすると、そもそもオブジェクトから辞書への変換は面倒くさそうだ。

これをよしなにやってくれるのがbpmappersである。詳しくはドキュメントを見てもらうとして、簡単な例を紹介しようと思う。

前提

まず SQLAlchemyで下記のようなモデルがあるとする。Entry と Comment は 1:N の関係にある。

# declare models 
class Entry(Base): 
    __tablename__ = 'entries'  
    id = Column(Integer, primary_key=True)   
    text = Column(String)  
    created_at = Column(DateTime, default=datetime.now, nullable=False) 

    comments = relationship('Comment') 

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    entry_id = Column(Integer, ForeignKey('entries.id'), nullable=False)
    text = Column(String)
    created_at = Column(DateTime, default=datetime.now, nullable=False)

このように定義すると「Entry.comments」は下記のようにCommentオブジェクトのリストとして取得できる。

# select data 
entries = Entry.query.all() 
for entry in entries:
   for comment in entry.comments: 
        print ' comment.id: ' + str(comment.id)
        print ' comment.text: ' + comment.text
        print ' comment.created_at: ' + str(comment.created_at)

この入れ子になったオブジェクト構成をbpmappersを使って簡単にJSONにしてみる。

Mapperの定義

# declare mappers
class CommentMapper(Mapper):
    id = RawField()
    entry_id = RawField()
    text = RawField()
    created_at = RawField(callback=lambda x:x.strftime('%Y-%m-%d %H:%M:%S'))
                                                                            
class EntryMapper(Mapper):
    id = RawField() 
    text = RawField() 
    created_at = RawField(callback=lambda x:x.strftime('%Y-%m-%d %H:%M:%S'))
 
    comments = ListDelegateField(CommentMapper) 

#convert to json  
print json.dumps(EntryMapper(Entry.query.all()).as_dict(), indent=2)

これで終わり。 Mapperを定義するだけ。 あとはas_dict()でよしなに辞書にしてくれる。実行結果はこんな感じ

{
  "created_at": "2012-02-08 00:06:12", 
  "text": "entry text", 
  "id": 1, 
  "comments": [
    {
      "created_at": "2012-02-08 00:06:12", 
      "text": "comment1 text", 
      "entry_id": 1, 
      "id": 1
    }, 
    {
      "created_at": "2012-02-08 00:06:12", 
      "text": "comment2 text", 
      "entry_id": 1, 
      "id": 2
    }
  ]
}

Mapperクラスを継承して、必要なフィールドを定義するだけで目的が達成できてしまった。RawFieldはオブジェクトが持つ同名のプロパティをそのままセットしてくれる。callbackも呼べるので適宜データを加工した上でセットすることも可能。

ポイント は ListDelegateField 。 リストの各要素に対するマッピング処理を別のMapperクラスに委譲できる。この例でいうと、commentsの各要素であるCommentオブジェクトのマッピング処理は、CommentMapperに任せてる。

まとめ

駆け足で説明したけど、まとめるとこんな感じ

  • Mapperを定義するだけで、オブジェクト自身のデータをゴニョゴニョしなくていいのは大変嬉しい。
  • DelegateFieldがある事で、ネストしたデータ構造でも安心して使える。
  • Modelクラスと1 : 1 になるように掛けるので、ごちゃごちゃしない

以上おわり。

おまけ

コードの断片だと分かりにくいと思うので、サンプルコードをまるっと貼付けておく。

#!/usr/bin/env python
#-*- coding:utf8 -*-

import json 
from datetime import datetime 

from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

from bpmappers import Mapper, RawField, ListDelegateField 

# create base 
engine = create_engine('sqlite://', convert_unicode=True, echo=False) 
db_session = scoped_session(sessionmaker(autocommit=False,  
                            autoflush=False,
                            bind=engine)) 
Base = declarative_base() 
Base.query = db_session.query_property()

# declare models
class Entry(Base):
    __tablename__ = 'entries' 
    id = Column(Integer, primary_key=True) 
    text = Column(String) 
    created_at = Column(DateTime, default=datetime.now, nullable=False)

    comments = relationship('Comment')

class Comment(Base): 
    __tablename__ = 'comments' 
    id = Column(Integer, primary_key=True)  
    entry_id = Column(Integer, ForeignKey('entries.id'), nullable=False)  
    text = Column(String) 
    created_at = Column(DateTime, default=datetime.now, nullable=False) 

# declare mappers
class CommentMapper(Mapper): 
    id = RawField() 
    entry_id = RawField()
    text = RawField() 
    created_at = RawField(callback=lambda x:x.strftime('%Y-%m-%d %H:%M:%S'))

class EntryMapper(Mapper): 
    id = RawField() 
    text = RawField() 
    created_at = RawField(callback=lambda x:x.strftime('%Y-%m-%d %H:%M:%S'))
    comments = ListDelegateField(CommentMapper)

# insert data 
Base.metadata.drop_all(bind=engine)
Base.metadata.create_all(bind=engine)

db_session.add(Entry(text='entry text'))
db_session.commit()
db_session.add(Comment(entry_id=1, text='comment1 text')) 
db_session.add(Comment(entry_id=1, text='comment2 text')) 
db_session.commit()

# select data
print '-' * 10 
entries = Entry.query.all() 
for entry in entries: 
    print 'entry.id: ' + str(entry.id)  
    print 'entry.text: ' + entry.text 
    print 'entry.creted_at: ' + str(entry.created_at) 
    for comment in entry.comments: 
        print ' ' + ('-' * 9)
        print ' comment.id: ' + str(comment.id)  
        print ' comment.text: ' + comment.text
        print ' comment.created_at: ' + str(comment.created_at)

# convert to json
print '-' * 10
print json.dumps(EntryMapper(entries).as_dict(), indent=2) 

ディレクトリ内のファイルを作成時刻でソートして連番をつけてリネームする

という感じのブログを見つけたので自分だったらどうやるかなぁと思ってやってみる。

前提

とりあえず上のブログを見習って、なるべく限定的な条件にして、お手軽にくむ

  • 再帰的にディレクトリを辿るとかいらない。
  • リネーム対象はファイルのみ
  • 拡張子はリネーム後でも変わらずに
  • リネームする時のフォーマットは、決めうちで
  • ソートは昇順のみで良い
  • バックアップとかも考慮いれない

こうした

#!/usr/bin/env python
# coding: utf-8 

import os
import sys
import stat

DIR_PATH = "/Users/hogehoge/hogehoge" 
RENAME_PATTERN = "IMG_%05d%s"

if __name__ == "__main__":
    if not os.path.isdir(DIR_PATH): 
        sys.exit("ERROR: '%s' is not directory." % DIR_PATH)

    index = 0 
    (root, dirs, files) = next(os.walk(DIR_PATH.rstrip(os.sep))) 
    targets = dict([(os.stat(os.path.join(root, f)).st_ctime, f) for f in files])
    for st_ctime, filename in sorted(targets.items()): 
        index += 1
        (name, ext) = os.path.splitext(filename) 
        os.rename(os.path.join(root, filename), os.path.join(root, RENAME_PATTERN % (index, ext)))

余談

  • ディレクトリセパレータは os.sepでとれる事をしった
  • os.path.splitextも初めてしった。

追記(2012/02/05)

  • 海原雄山なのか戦国大名なのか段々見分けるのが困難になってきた御館様(id:imagawa_yakata)に下記のようにTwitterで教えてもらいました。いつもありがとうございます。m( _ _ )m
  • といわけで上記のコードを下記のように修正しました。
-    (root, dirs, files) = os.walk(DIR_PATH.rstrip(os.sep)).next()  
+    (root, dirs, files) = next(os.walk(DIR_PATH.rstrip(os.sep)))

利用可能なモジュール一覧を取得する

ふとPythonで利用可能なモジュールの一覧を取得したくなったのでメモ。

条件

ここでいう利用可能なモジュール および パッケージ一覧とは、以下のようなものとする。

  • ビルトインで組み込まれてるやつ
  • site-packages にインストールされてるやつ
  • virtualenvなどの仮想環境などにインストールされてるやつ
  • というかパスが通ってて、モジュール、パッケージとして認識されるヤツ全て
  • 確認したいのじゃなくてリストとかのデータとして扱いたい

こんなん僕が求めてるのとちゃう

求めてるものとはなんか違った夢の残骸達

sys.builtin_module_names

文字通り、ビルトインで入ってるやつの一覧。足りない

import sys                                                                      
print sys.builtin_module_names

#=> ('__builtin__', '__main__', '_ast', '_codecs', '_sre', '_symtable', '_warnings', '_weakref', 'errno', 'exceptions', 'gc', 'imp', 'marshal', 'posix', 'pwd', 'signal', 'sys', 'thread', 'xxsubtype', 'zipimport')
sys.modules

実行時にロード済みなやつの一覧。まだ足りない。とういかロードしてない組み込みのヤツは出てこない。

import sys                                                                      
print sys.modules.keys()

#=>['cStringIO', 'copy_reg', 'encodings', 'site', '__builtin__', '__main__', 'encodings.encodings', 'abc', 'posixpath', 'flaskext', '_weakrefset', 'errno', 'pprint', 'encodings.codecs', '_abcoll', 'types', '_codecs', 'new', '_warnings', 'genericpath', 'stat', 'zipimport', 'encodings.__builtin__', 'warnings', 'UserDict', 'encodings.utf_8', 'sys', 'codecs', 'os.path', 'signal', 'linecache', 'posix', 'encodings.aliases', 'exceptions', 'os', '_weakref']

help('modules')

めっさ一杯出た。大分近い。仮想環境にいれたヤツとかもちゃんと拾ってくれてる。。。けど結果が標準出力されるのでなんか微妙

$ python -c "help('modules')"

Please wait a moment while I gather a list of all available modules...

ArgImagePlugin      _Mlte               difflib             pimp
Audio_mac           _MozillaCookieJar   dir_help            pip
BaseHTTPServer      _OSA                dircache            pipes
Bastion             _Qd                 dis                 pkg_resources
BdfFontFile         _Qdoffs             distutils           pkgutil
BeautifulSoup       _Qt                 django_assets       platform
BeautifulSoupTests  _Res                doctest             plistlib
BmpImagePlugin      _Scrap              dprint              popen2
...
...

これや僕が求めてたのはこれなんや

python -c "help('modules')"」の結果は、「pydoc modules」と等価である。というかhelpは多分pydocを見てる。というわけで、pydoc.pyをあら探しする。

であった。それが「pydoc.ModuleScanner」とかいうヤツ。pydoc.pyではこんな使いかたしてた。

1879             modules = {}
1880             def callback(path, modname, desc, modules=modules): 
1881                 if modname and modname[-9:] == '.__init__': 
1882                     modname = modname[:-9] + ' (package)' 
1883                 if find(modname, '.') < 0: 
1884                     modules[modname] = 1 
1885             def onerror(modname):
1886                 callback(None, modname, None)
1887             ModuleScanner().run(callback, onerror=onerror)
1888             self.list(modules.keys())

これを使って「pydoc modules」は実現されていた。単純に一覧が欲しいだけので、コピってこんな感じに変えてみた。

#!/usr/bin/env python
# -*- coding: utf-8 -*

from pydoc import ModuleScanner
from string import find

modules = []

def callback(path, modname, desc, modules=modules):  
    if modname and modname[-9:] == '.__init__': 
        modname = modname[:-9]  
    if find(modname, '.') < 0 and modname not in modules:
        modules.append(modname) 

def onerror(modname): 
    callback(None, modname, None)

ModuleScanner().run(callback, onerror=onerror)
print modules

これを実行すると。。。

$ python all_modules.py
['__builtin__', '_ast', '_codecs', '_sre', '_symtable', '_warnings', '_weakref', 'errno', 'exceptions', 'gc', 'imp', 'marshal', 'posix', 'pwd', 'signal', 'sys', 'thread', 'xxsubtype', 'zipimport', 'all_avaliable_modules', 'all_avaliable_modules2', 'ascii_to_ebcidic', 'blinker_sample', 'bool_judge', 'cast', 'class_name', 'converting_byte', 'datetime_test', 'dict_key_map', 'dict_merge', 'diff_time', 'dir_help', 'dprint', 'fib', 'file_abs_path', 'fold_test', 'func_args_disruptiv
...
...

おー。いっぱいでてきた。ビルトインも、仮想環境も、カレントディレクトリのヤツも全部出てきたw

サブモジュール、パッケージも一覧に含める

上のヤツでは、トップレベル(と呼んでいいんだろうか?)のパッケージとモジュールの一覧しか表示されないようになってる。サブモジュール・パッケージも含めて一覧で表示したかったら。「callback」関数をこう変えればいい。

def callback(path, modname, desc, modules=modules):
    if modname and modname[-9:] == '.__init__':
        modname = modname[:-9]
-    if find(modname, '.') < 0  and modname not in modules:
-        modules.append(modname)
+    if modname not in modules:
+        modules.append(modname)

愚行

ModuleScannerに辿り着く前は、もういいやって気分で「pydoc modules」の標準出力をパースして強引にリストを取得しようとしている愚かな僕がいました。けど「sys.path.insert」で追加したパスのモジュールとか取得できないじゃんとか思ってやめた

#!/usr/bin/env python                                                           
# -*- coding: utf-8 -*-                                                         

import pprint
import commands
import re
ret = commands.getoutput('python -c "help(\'modules\')"')
module_list = []
start = False
for line in ret.split('\n'): 
    if re.match(r'[^ ]+[ ]+[^ ]+[ ]+[^ ]+[ ]+[^ ]+$', line): 
        start = True
    if start and line == '':
        break
    if not start:
        continue

    line = re.sub(r'[ ]+',r' ', line)  
    for module in line.split(' '):
        if module.strip(' ') != '': 
            module_list.append(module.strip(' '))

module_list.sort() 
pprint.pprint(module_list) 

追記(2015/3/4)

コメント欄で 教えて貰った「pkgutil.iter_modules」を使うとインストール済みのやつとかを一覧でみることができるようです。いい感じですね。:id:hirokiky さんありがとうございます。

>>> import pkgutil
>>> for m in pkgutil.iter_modules():
...     print m
...
(<pkgutil.ImpImporter instance at 0x103103e18>, 'UserDict', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, '_abcoll', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, '_weakrefset', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, 'abc', False)
(<pkgutil.ImpImporter instance at 0x103103e18>, 'codecs', False)

余談

  • ModuleScannerのonerrorという関数は、何かしらのエラーでimportできないパッケージに対して呼び出される。(モジュールではなく)

Google Appengine で datastore_admin が使えなくなった

GoogleAppengine の Python SDK の バージョン1.6.2が出ましたね。早速upgradeしてみたら。下記のようなエラーに遭遇したのでメモ

datastore_admin が無いよとエラー

dev_appserver.py を叩いたら下記のようなエラーがでました。

google.appengine.ext.builtins.InvalidBuiltinName: datastore_admin is not the name of a valid builtin.
Available handlers are: admin_redirect, appstats, default, deferred, django_wsgi, remote_api

どうやらビルトインの中から、datastore_adminは無くなったようです。もう少し正確に言うと、Python2.7 で開発してる場合のみdatastore_adminが使えないっぽいです(2.5で試してないけど) 。リリースノートにもそのように書いてありますた。

2.7で開発をしているというのは、app.yamlでruntimeを「python2.7」にしているという事です。

runtime: python27

というわけでapp.yamlから削ったった。

builtins:
- datastore_admin: on #<- 削った

ASCII to EBCDIC(符号付き数値)

数年前、全銀協のフォーマットで且つ、EBCDICコードで相手側のシステムに送信しなければならないという案件と格闘していた。全銀協とは全国銀行協会の略で、全国の銀行は、ここが定めるプロトコルおよび、データフォーマットで振込情報のやりとりやらなんやらをしている。

ファームバンキングと全銀形式(全銀ファイルフォーマット) - Shoulder.jp

ASCIIコードからEBCDICコードへの変換

そもそも僕はウェッブケー一本でやってきたのでEBCDICコードをどうやってLinux上で生成するのかわからなかった。調べた結果、ddコマンドを使えば簡単にデータファイルの変換が出来る事がわかった。

dd if=./input.txt of=./output.txt conv=ebcdic

数値データはサイン(符号)付きで

ddコマンドで変換を掛ければそれで済むと思った。ところがEBCDICの数値データはサイン(符号)付きでなければ駄目と言われた。(振込情報には金額のデータがあったので)

「はぁ?」

EBCDICコードでは数値データを 表現する場合、最終桁の数値は8bitのウチ上位4ビットを符号を示すものにしなければならなかった。仕様書にはCOBOLで言う所のS9という型で表記されていた。

ddコマンドは単に文字列をEBCDICコードに変換するだけなので、符号付きの数値データを当然考慮してくれなくて超困った。

ASCIIコードの数値を強引に変換

  • 最終桁の上位4bitが違うという事は = ASCIIコード上では最終桁が数値(0-9)では無くなる事を意味する。
  • 正負によって上位4bitはの値は変わってくる。

以上の手がかりをもとに、バイナリエディタと、EBCDICコード表もとにEBCDICの符号付き数値がASCIIコードで表現された時には最終桁はどんな文字列になるのかを泣きながら調べた。

そして下記のような関数を書いた(当時はPHPで書いたけど)。そしてデータファイル(上の例で言えばinput.txt)を書き出す前に、数値データはこの関数を通して書き出すようにした。

def ascii2ebcdic_sign_number(number):
    plus_codes = {0:'{', 1:'A', 2:'B', 3:'C', 4:'D', 5:'E', 6:'F', 7:'G', 8:'H', 9:'I'}
    minus_codes = {0:'}', 1:'J', 2:'K', 3:'L', 4:'M', 5:'N', 6:'O', 7:'P', 8:'Q', 9:'R'}
    codes = minus_codes if number < 0 else plus_codes
    return str(abs(number))[:-1] + codes[abs(number) % 10]

print ascii2ebcdic_sign_number(1001)  #=> 100A
print ascii2ebcdic_sign_number(-1001) #=> 100J

こうやって変換した結果をddコマンドで一発変換すれば、EBCDICコードでの数値データを表現できた。ブログに書くとさらっとだけど、この後にさらにベンダー出してる商用の暗号化ソフトとか、全銀クライアントとの格闘があって、終わった直後は放心というか憔悴してたと思う。

余談

  • 実は数年前にもブログに書いたけど、それはもう無くなってしまったのでサルベージした。
  • もしかしたら僕みたいに経験も知識もなく、同じような状況に陥ってる人がいたら、少しでもその人の助けになるようにと思って書いた。
  • ddを使わなくても、Pythonで文字列をEBCDICに変換する事はできるだろう。けど符号付き数値を表現するためには結局上のような事をやらなければならないと思ってる。もしより良い方法があれば追記したいので教えてください。

「これを必要とする貴方の心が折れる前にココに辿り着く事を祈る」