コマンドラインがさらに便利になるfillinコマンドを作りました!

fillinというコマンドラインツールを作成しました。

コマンドの一部を変数化して、別の履歴に保存しておけるツールです。 ステージング環境と本番環境のように、同じコマンドで複数の環境を切り替えるのに便利です。

zshの本 (エッセンシャルソフトウェアガイドブック)

zshの本 (エッセンシャルソフトウェアガイドブック)

  • 作者:広瀬 雄二
  • 発売日: 2009/06/17
  • メディア: 単行本(ソフトカバー)

どうして作ったの

コマンド履歴って便利ですよね。 私はよくコマンド履歴からコマンドを選んで実行しています。 シェルに付属しているデフォルトの履歴を使っている方もおられるでしょうし、fzfやpecoのようなインタラクティブな絞り込みを行なっている方もいるでしょう。

私が一番困っていたのが、認証キーの扱いです。 webアプリを作っていてcurlで素早く確認するときに、認証キーやアクセストークンを打つことがあります。 アクセストークンのようなランダムな英数字は、fzfのようなfuzzy searchとかなり相性が悪いものです。 最近fzfに乗り換えてインターフェースは気に入っていたのですが、トークンを入れてcurlする癖を直さないといけないなぁと思っていました。

いや、トークンを直に打つなよ…っていうご意見はごもっともです。 ライブコーディングする前に履歴消さないといけませんし。 Basic認証ならば標準入力で指定できるんですけど、ヘッダーは (たぶん) できませんよね。 認証キーは何かしらの履歴には残っていて簡単に呼び出したいけど、シェルの履歴には入れたくない… この程度のことにシェルスクリプトを書くのも面倒だし、ファイルに一個ずつ保存していちいち展開するのもかわいくない…

トークンをコマンドに直接書きたくない以外にも、コマンドの一部を別管理したいと思うことはたくさんあります。 データベースに繋ぐ時、データベースのホスト名とデータベース名はセットで管理したいですよね。 awsコマンドのプロファイルだってシュッと切り替えて同じコマンドを実行したくなることもあります。

コマンドの一部を「変数」にして、それを「展開して実行」してくれるコマンドがあれば良いのではないか? シェルの履歴が汚れることはないし、履歴をfuzzy searchしてもおかしなことにはなりにくい。 変数に埋めた値の履歴をローカルに保存して矢印キーとかで呼び出せれば便利そう…

はい、それがfillinです。

brew install itchyny/tap/fillin

または

go get github.com/itchyny/fillin

でインストールできます。

導入編

fillinコマンドの使い方はとても簡単です。 いつも打つコマンドにfillinとつけて、「変えたいところ」をテンプレートにしてあげるだけです。 例えば、次のように打ってみましょう。

 $ fillin echo {{message}}
message: 

messageは何かと聞かれました。適当に打ってみます。

message: こんにちは、世界!
こんにちは、世界!

こんにちは! fillinコマンドは、messageの場所をユーザーに入力してもらい、その値を埋めてコマンドを実行するだけです。

そうです、{{message}}というところを「埋めて (fill-in)」から実行する、それがfillinコマンドです。

もちろん変数はいくつも使うことができます。

 $ fillin echo {{foo}} {{bar}} {{baz}}
foo: 変数を
bar: こういうふうに
baz: 入力するよ
変数を こういうふうに 入力するよ

かわいい。

普段使っているコマンドをテンプレート化してfillinとつけるだけなので、例えば環境変数を含んだコマンドも実行できます。

 $ fillin LANG={{lang}} date
lang: en_US
Mon Jun 12 08:22:55 JST 2017
 $ fillin LANG={{lang}} date
lang: ja_JP
2017612日 月曜日 082252秒 JST

便利!

実践編

いつも打つコマンドの中で、ホスト名のように環境によって切り替えるところをテンプレート化することができます。

 $ fillin psql -h {{psql:hostname}} -U {{psql:username}} -d {{psql:dbname}}
[psql] hostname: localhost
[psql] username: example-user
[psql] dbname: example-db

psql (9.6.3)
Type "help" for help.

example-db=>

各値を入力して決定すると、いつものようにコマンドが実行されます。 入力した値はまとめて履歴に残るので、同じコマンドから上矢印キーで呼び出すことができます。

 $ fillin psql -h {{psql:hostname}} -U {{psql:username}} -d {{psql:dbname}}
[psql] hostname, username, dbname: localhost, example-user, example-db

もちろん、ステージング環境用、本番用のように複数の設定を残すことができます。 psql:という同じ「スコープ」を持つ変数は、組になって履歴を呼び出すことができるのが便利なところです。

webアプリのAPIの動作確認でcurlを使う人ならば、次のようにbase-urlaccess-tokenを管理することができます (認証方法はサンプルです)。

 $ fillin curl {{api:base-url}}/api/info -H 'Authorization: Bearer {{api:access-token}}'
[api] base-url, access-token: example.com, accesstokenabcde012345

この例でもapi:というスコープをつけたので、ホストと認証キーがステージングと本番でごっちゃになることはありません。

何が直交したデータなのかということを考えるのはとても大事なことです。 ローカルの環境、ステージング環境、本番環境によって、ベースURLと認証キーは異なります。 そして、API/api/info, /api/service, /api/account ... といくつかあります (URLは適当です)。 どのAPIも、どの環境に対しても叩くことができます。 つまり環境とAPIのエンドポイントは直交した概念です。

「このAPIのエンドポイントにcurlする」ということはシェルの履歴に残します。 「ステージングの認証キーや本番のキー」といった情報は、fillinの履歴に残します。 これによって、シェルにn*m個の履歴が残ることなく、コマンド履歴を探すのが楽になります。 新しいAPIのエンドポイントを作ったときも、開発時にローカル環境に対して打っておけば、後は同じコマンドでステージング、本番と切り替えるだけで動作確認できます。

こういう話はデータベース、API開発以外にもたくさんあると思います。 例えばawsコマンドの--profile引数ですね。

 $ fillin aws --profile {{aws:profile}} ec2 describe-instances
[aws] profile: aws-profile-example

EC2 (など) に何をするか×アカウントのプロファイルが直交概念ですね。 他にも応用が効く考え方だと思うので、是非よい使い方を考えてみてください。

Go言語による並行処理

Go言語による並行処理

まとめ

コマンドには「どこに」「何を」するかの二軸があります。 「どのホストのどのポートに」「Redis CLIでログインする」とか、「AWSのどのプロファイルの管理している」「EC2のインスタンスを一覧する」、「ステージング環境 (あるいは本番環境) の」「APIを叩いてレスンポンスを調べる」といった具合です。 fillinを用いると、この2つをきれいに分離し、コマンド履歴の管理が簡単になります。

コマンドには「何をするか」を打ちます。

ホスト名、ベースURL、認証キー、プロファイルといった「環境 (どこに)」はfillinで切り替えます。

このように運用すると、コマンドを打つのがとても楽になります。 いや、楽になりそうです… 前の木曜にアイディアを思いついて、この土日でようやく動いたばかりなので、まだがっつりは使ってないんです… でも、きっと便利なはずなので、よかったら使ってみてください。 私もしばらくfillin運用すると思います。 沢山の人に使っていただくと私がとても喜びます。

実際に実行されたコマンドが履歴に残らないのは困る? ご安心ください。 fillinコマンドは実行したコマンドをタイムスタンプ付きで~/.config/fillin/.fillin.histfileに保存しています (この場所が適切なのかという話はありそうだけど、まぁとりあえず…)。 zshのコマンド履歴を真似して出力しているので、fillinをやめたいとなったら (そんな…><)、.histfilegrep -v fillinし、.fillin.histfile.histfileに追記しソートするといい感じになるはずです。 よかったですね。

宣伝

はてなでは、開発時に不便なことがあったらツールを自分で作って解消する、そんな情熱あふれるエンジニアを募集しています。

エンジニア以外にも様々な職種のご応募お待ちしております。

こちらの記事もどうぞ。私です。

Vimに自分の書いたパッチが取り込まれた!

Vim 8.0.0623に私の書いたパッチが取り込まれました。 わーい ∩(>◡<*)∩ わーい!

もともとのきっかけは、自分のプラグインを開発している中で、[\u3000-\u4000]という正規表現に対する挙動がset re=1set re=2で違うことに気がついたのです。 Vim正規表現エンジンを2つ積んでいる恐ろしいエディターなのですが、この2つの正規表現エンジンの挙動に微妙な違いがあることに気がつきました。 新しいNFAエンジンではエラーは出ませんが、古いエンジンではエラーが出ます。 古い正規表現エンジンでは、[a-z] みたいなパターンで、[\u3000-\u4000]のように差が大きすぎるとエラーを吐くのです。 この挙動の違いはまぁいいんです、エンジンの仕組みがぜんぜん違うので… ただ、この時のエラー番号E16でヘルプを引くと、コマンドのrangeに関するエラーなんです。 例えば :0buffer のようなケースですね。 ソースコードを覗いてみると、E16は [z-a] のようなパターンに対するエラーにも使われており、コマンドのrangeに関するエラーと正規表現の文字クラスに関するエラーは分けたほうが良さそうだと気が付きました。

エラー番号E16を正規表現のエラーに使いまわすのはおかしいと報告したところ、Bram氏に「昔はメモリーをあまり使わないよう、エラーメッセージが増えすぎないようにしていたんだ」と教えてもらいました。 「どう変えたらよくなるかを提案してね」ということだったので、エラーメッセージを追加したパッチを送りました。 この最初のパッチでは[z-a][\u3000-\u4000]の2つのエラーを同じメッセージにしていたのですが、Bram「メッセージ2つ分けてちょうだい。あとテストも書いてくれたらうれしいな」とのことだったので、メッセージを分けてテストを書いて送り返しました。 それから半月くらい音沙汰なかったのですが、最近ようやくマージされました。 返事がなくても気長に待つことですね。 あと、ヘルプなどのruntimeファイルはまとめて更新されるはずです。

最新バージョンを入れて動作チェックして、自分が書いたエラーメッセージが表示された時はホッとしました。 VimのE944とE945はな、父さんが作ったんだよって息子に自慢できますね、結婚してないけど。 世界中の開発者が使う道具の中のどこかに自分が書いたコードが入っていると思うとワクワクしますね! そんな感じです!じゃあねっ!

lightline.vimのREADME.mdを書き直しました

lightline.vimVimのステータスラインをいい感じにしてくれるプラグインです。 作って四年弱経つんですね。 おかげさまで多くのユーザーさまに使っていただいています。

itchyny.hatenablog.com

github.com

このREADME.mdを最近書き換えました。

………

それだけかいな!って感じなのですが、いろいろと大変でした… 主に精神的に… つらい…

  • プラグインを作った勢いと使って欲しいという強い思いで、プラグインを作ってすぐにREADME.mdをかなり詳しく書いていた
  • そのために当初の設計や実装時の思いが強く出ていたが、初めてプラグインを触る人にとっては読みにくい文章になってしまっていた
  • パッチをあてたフォントを使わないとプラグインが使えないと誤解されることがあった。実際、スクリーンショットの多くがパッチをあてたフォントを使った状態で撮っていた。
  • スクリーンショットの画質がかなり悪かった

とにかくいろいろと書き換えたいと思いつつ何か月も経ってしまったのですが、なかなか手が進みませんでした。 一応プラグインを作ってから三年以上、沢山の方にREADME.mdを読んでいただいていて、そこそこ機能していました。 そこそこ長い英語の文章で書いてしまったので、これをまた全体を書き直すのが億劫になっていたというのもあります。

いや、ほんと、一旦書いた英語の文章を、いい感じに書き直すの大変ですよ… しかもそれが趣味プロダクトのREADMEで、書き換えるのが必須でもなくて…

なんて思っていたのですが、やはりいろいろとよくないことも多かったので、最近一気に書き直しました。

  • 関数コンポーネントを使うことで、短い設定でいろいろと見た目を変更できることを伝えるようにしました。
  • パッチフォントに関する言及は辞めました。引き続き特殊なフォントのグリフを使うことはできますが、ポータビリティは悪いのであまりオススメはしません。
  • 誤解を生みやすい component_visible_condition の説明はやめました。関数コンポーネントを使えば十分です。
  • スクリーンショットをきれいに撮り直しました。

そんなこんなで少しは読みやすくなったかな〜と思います。

ヘルプの方も近いうちに書き換えていきます。 こんな感じです! じゃあね!

Haskellで10を作るプログラムを書いてみたので動画で公開してみた

最近Rui Ueyamaさんがコーディング動画をアップされているのを見て、私も動画を撮りたくなりました。題材をしばらく考えていたんですが、10を作るプログラムを書いてみることにしました。

www.youtube.com

後から見直すと色々ミスっていて、わりと焦っていることがわかります。なにかの癖で適当に bc -l とかやったのだけど、音声をあてる時は関係ないオプションだと勘違いしてしまいました。確かにglobされていたのはよくなかったけど、 echo '5 / (5 / (5 + 5))' | bc -l とかで考えてみると -l も必要なんですよね。2つの問題が起きていて混乱してしまった…

もともとはProject Euler 93を昔解いたことがあったので、こういう系の問題と分数の扱い方とか括弧の付け方みたいなところはイメージありました。ただ、Project Eulerの問題は式木を表示する必要がなかったので、式の型を作ってなかったんですよね。それで今回改めてコードを書いていたらなんか意外と色んなところでハマってしまいました。

最近のHaskellは少し雲行きが怪しいなという印象がありますね。ここ10年で出てきた新しい言語より勢いがないのは否定できないし、若者があまり興味を示さないのも分かる気がします。私も最近はRustばかり触っているのですが… なんかHaskellの楽しさを直接伝えるものがあまりないんですよね。ブログエントリーのレベルも二分化されてしまっている印象があります。

私は昔、Project EulerCodeforcesの問題を順番に解いていた時期があって、Haskellを書いていて楽しかったなぁと思い出すのはその頃の思い出なのです。まぁ数学のパズルが好きじゃないとささらないのかもしれませんが…

Haskellerの皆さんはどういうところに一番関心がありますか?Haskellを書いていて楽しい時はどういう瞬間でしょうか。若い世代に興味を持ってもらうにはどういうものがあると良いのでしょうか。

追記: 括弧の省略アルゴリズム

括弧を省略しようとして動画では失敗してしまいました。改めて落ち着いて括弧が必要なケースを調べてみると、以下のようになりました。

  • 左辺の括弧が必要なケース: 演算子が乗算か除算で、かつ左辺が加算か減算かのどちらか
  • 右辺の括弧が必要なケース: 演算子が減算か乗算で、かつ右辺が加算か減算、もしくは演算子が除算で右辺が二項演算子

というわけで、次の実装で大丈夫だと思います。

isAddOrSub :: Expr -> Bool
isAddOrSub (NumInt _) = False
isAddOrSub (BinOp (Op op _) _ _) = op == "+" || op == "-"

isBinOp :: Expr -> Bool
isBinOp (NumInt _) = False
isBinOp (BinOp _ _ _) = True

instance Show Expr where
  show (NumInt n) = show n
  show (BinOp op@(Op opstr _) lhs rhs) = lparen (show lhs) ++ " " ++ show op ++ " " ++ rparen (show rhs)
    where lparen | (opstr == "*" || opstr == "/") && isAddOrSub lhs = \str -> "(" ++ str ++ ")"
                 | otherwise = id
          rparen | (opstr == "-" || opstr == "*") && isAddOrSub rhs || opstr == "/" && isBinOp rhs = \str -> "(" ++ str ++ ")"
                 | otherwise = id

後は自明な左辺右辺の入れ替えの除去くらいですかね。いやはやめんどくさそうだ…

Haskellの普及について色々と考えていたんですが、中高生にプログラマーってすごい、なりたいって思わせるのにライブコーディング動画って有用なんじゃないかと思いました。どうせ若い頃に興味を持つ言語はころころ変わるので、あまりそこにこだわってもよくなくて、むしろもう少し若い世代にプログラミングにどう興味をもってもらうかというところに力を入れるとソフトウェア産業も発展していくのではないでしょうか… うまい問題設定のライブコーディング動画って子供にとって結構刺さると思うんですよねぇ。ここまで考えたところでテトリス一時間で実装動画を思い出しました。これは強烈でしたねぇ。

gitのファイル変更日時をファイルのアクセス日時に設定

普段使っているファイラーはファイルのアクセス日時でソートされるように設定しています。大きめのリポジトリをcloneしてコードを読む時に、意外とファイルの最終変更日時が参考になったりします。仕事で使うリポジトリや、定期的にpullしているなら、徐々に変更のないファイルはファイラーの下の方に移動していく (上の方からアクセス日時の降順として) のですが、cloneしたばかりだとこうは行きません。

要はgitリポジトリ内の各ファイルのアクセス日時を、そのファイルのgit履歴上での最終変更日時に戻したいという気持ちになるわけです。そうするとファイラー上でもいい感じにファイルがソートされるのです。

#!/usr/bin/env bash

if command -v gdate >/dev/null 2>&1; then
  DATE=gdate
else
  DATE=date
fi

while IFS= read -r -d '' file; do
  logtime=$(git log -1 --format=%ci "$file")
  mtime=$("$DATE" -d "$logtime" +%Y%m%d%H%M.%S)
  echo "$(printf "%-80s" "$file")"$'\t'"$logtime"$'\t'"$mtime"
  touch -t "$mtime" "$file"
done < <(git ls-files -z)

実行してみるとこういう感じ。

 $ git-touch
.ctags                                                                              2015-08-28 10:47:55 +0900   201508281047.55
.tmux.conf                                                                          2016-02-05 08:43:46 +0900   201602050843.46
.vimrc                                                                              2017-05-01 18:38:35 +0900   201705011838.35
.vimshrc                                                                            2017-03-10 20:45:22 +0900   201703102045.22
.zshrc                                                                              2017-04-20 00:32:39 +0900   201704200032.39
 $ stat -x .zshrc
  File: ".zshrc"
  Size: 10518        FileType: Regular File
  Mode: (0644/-rw-r--r--)         Uid: (  501/ itchyny)  Gid: (   20/   staff)
Device: 1,4   Inode: 203408150    Links: 1
Access: Thu Apr 20 00:32:39 2017
Modify: Thu Apr 20 00:32:39 2017
Change: Sun May  7 01:03:05 2017

atimeとmtimeがgitでのファイル更新時刻になりました。やったー!

git logはタイムゾーン込みで保存しているようなので、touchする前にローカルタイムゾーンに変換しないといけない。最初はsedタイムゾーン取り除いていたけれど、流石に乱暴だったのでdateコマンドを挟むようにしました。

できたーと思ってふと検索してみるといっぱいでてきた… まぁみんなやるよねぇ…

macOSの (というかFreeBSDの) dateコマンドの-dは日付を指定するオプションじゃないのが罠っぽい。date -jfであれこれしたほうが本当はいいけれど、coreutilsのgdateに逃げた。あとスペース含むファイル名の扱いにはご注意を。

dateコマンドのところはPythonでやろうかと思ったけれど、実装してみたら意外とオーバーヘッドが大きかった。まぁオーバーヘッドと言っても二倍時間がかかるくらいなんですが、リポジトリ内のファイルを全部なめて更新していくので、できれば速いほうが嬉しい。速度を取るかポータビリティを取るかはいつも難しい。PythonPythonで2.7と3系両方で動くスクリプトを書ける自信はない。ところで jq fromdateiso8601 を使う手も考えたのですが (ポータビリティ的には良いアイディア)、タイムゾーン部分をパースできなくて (Zしか対応してない)、なーにがISO 8601じゃと思いました。ああ、いい話だなぁ。それじゃーね!ばいばい!