Python 40行でbash/zshのヒストリファイルをマージするスクリプト
自分はzshユーザなのですが、色々なマシンで作業していると、各マシンのヒストリファイル.zsh-historyをマージしたくなることってありますよね。「あれ、あのコマンド、前に使ったはずなのに、コマンドヒストリにない...」と思ったら、他のマシンで作業した時に入力したコマンドだった、というような場合です。
Pythonのgeneratorやiterator周りの練習として、zshのコマンドヒストリファイル(.zsh-historyや.bash_history)をマージするmergehist.pyを、短めに40行で書いてみました。多分、書ける人が書けば、もっとずっと短く書けると思います。
コマンドヒストリファイルの構造は単純で、例えば、
: 1443359084:0;ls
といったように、基本、各行は
: UNIXタイムスタンプ:0;打ったコマンド
の羅列です。が、必ずUNIXタイムスタンプの昇順に並んでいなければなりません。また、末尾に、バックスラッシュを入れて複数行のコマンドを入力した場合は、「打ったコマンド」の部分に複数行がそのまま記録されますから、その辺も処理してやる必要があります。
使い方は、
python mergehist.py ヒストリファイル1 ヒストリファイル2 ...
という具合に、マージしたいヒストリファイルを複数(3個以上でもよい)、ちゃんとUNIXタイムスタンプの昇順などを考慮してマージされたものが標準出力に出てくる...はずです。
#!/usr/bin/env python import sys def histkey(line): return int(line[2:12]) def concat_multiline(it): it = iter(it) toyield = '' for line in it: toyield += line if line.strip().endswith('\\'): continue yield toyield toyield = '' def merger(*iterables, **kwds): iters = map(iter, iterables) tonext = [1] * len(iters) stack = [] prevpopi = -1 mykeyf = lambda x: kwds.get('key')(x[0]) if kwds.get('key') else None while True: for i, it in enumerate(iters): try: if tonext[i] > 0: stack.append((it.next(),i)) tonext[i] = 0 except StopIteration: tonext[i] = 0 stack.sort(key = mykeyf) if stack: x, prevpopi = stack.pop(0) tonext[prevpopi] = 1 yield x else: raise StopIteration() sys.stdout.writelines(l for l in merger(*[concat_multiline(line for line in \ open(sys.argv[i], r'r')) for i in range(1,len(sys.argv))], key=histkey))