17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Vimの隠しコマンド

Last updated at Posted at 2017-06-20

1.Vimを立ち上げる

$ vim

2.コマンドモードに入る

:

3.Ni!とタイプしエンター

:Ni!

4.結果は......

Do you demand a shrubbery?

なにこれ

モンティ・パイソン関連のジョークらしいです(参考:Wikipedia)。とは言っても私も元ネタを全く知らないのでよくわかりません。

検索してみるといくつかこのイースターエッグに言及した記事が見つかりました(他のイースターエッグも紹介されていて面白いです)。

詳細

この記事でやっていたVim scriptの高速化にあたってコードを眺めていると以下のような行を発見しました。

src/ex_docmd.c
/* Check for wrong commands. */
if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78)
{
    errormsg = uc_fun_cmd();
    goto doend;
}

ea.cmdは実行するExコマンドを表す文字列(qwなど)なのですが、なぜか文字リテラルではなく数値リテラルと比較しています。しかもその一方は8進数で書かれています。コメントにはCheck for wrong commandsとそれっぽいことが書かれていますが、なんだか妙な感じです。そこで、エラーメッセージを返しているらしいuc_fun_cmd()を見てみます。

uc_fun_cmd()

src/ex_docmd.c
    static char_u *
uc_fun_cmd(void)
{
    static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4,
                            0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60,
                            0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2,
                            0xb9, 0x7f, 0};
    int		i;

    for (i = 0; fcmd[i]; ++i)
        IObuff[i] = fcmd[i] - 0x40;
    IObuff[i] = 0;
    return IObuff;
}

!?
文字列をわざわざ""を使わずに、しかも16進数で書き、それからさらに各文字から0x40を引くというなんとも回りくどいことをしています。一体この文字列はなんなのでしょうか。せっかくなのでVimを使って確かめてみましょう。

まずfcmdの右辺の{}[]に置き換えます。surround系のプラグインを入れていれば一発です。
次に[]内の改行を取り除きます。Jコマンドを使うといいでしょう。
さらにya[とし、配列全体を無名レジスタにいれておきます。
最後にコマンドモードで:echo join(map(eval(@")[:-2], 'nr2char(v:val - 0x40)'), '')を実行します。

Do you demand a shrubbery?

冒頭のメッセージが復元できました。

メッセージの発生条件

さて、このエラーメッセージが表示されるのは、

src/ex_docmd.c
/* Check for wrong commands. */
if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78)
{
    errormsg = uc_fun_cmd();
    goto doend;
}

このif文の条件式、*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78が満たされた時です。ここでea.cmdはユーザーが入力したコマンドを表す文字列で、pはそのコマンドの次の文字を指しています。それでは015178を文字に直しましょう。

:echo nr2char(0151) " => i
:echo nr2char(78) " => N

最初の文字がN、次がi、コマンドの次に来るのが!ですから、これはつまり:Ni!です。これで最初の現象が解明できました。なお、:Ni!以外にも:Niiiiiiiiiii!:Nippon!:'<,'>Ni! foobarなどにも反応します。コマンドの名前がNiで始まり、!が指定されていれば引数があろうと範囲指定があろうとこのメッセージが表示されます。追記: 最新のバージョンでは修正されています。下記参照

コマンド名に関する注意事項

追記: patch 8.0.0656によりこの問題は修正されました。

patch 8.0.0656: cannot use ! after some user commands
Problem: Cannot use ! after some user commands.
Solution: Properly check for existing command. (Higashi Higashi)

内容が内容だけにProblem:やSolution:のところでジョークをかましてくるかと期待してたんですがいつもと同じ文章でちょっと残念......(言われてもわからないけど)。

注意: ここから先はversion 8.0.0655以前の内容です。

ここで少し気になるのが、ユーザーがNiというコマンドを定義した場合です。

:command! -bang Ni echo 'User-defined Ni'
:Ni " => User-defined Ni
:Ni! " => Do you demand a shrubbery?

あれ。定義されているかどうかは関係なくこうなるんですね。イースターエッグをhelpに書くわけにもいかないので(わざわざ難読化していることですし)全く知らない人が突然ハマりそうで怖いです。

引っかかりそうな単語

  • Niceなんとか
  • Nightなんとか
  • 9, 19, 90, ...
  • Nim language(奇しくもPython風の言語)
  • Nitroなんとか/Nickelなんとか(物質名はソフトウェアに結構ある気がする)
  • Ninja(ビルドツールのNinjaは大丈夫なんでしょうか)
  • その他多数(List all words starting with ni

Niceから始まるコマンドなんてもうすでにありそうで怖いですね。皆さんもコマンドを定義するときにはNiから始まっていないかどうかを確認しましょう。

17
7
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?