深酒とお昼寝で忘れる

深酒とお昼寝で忘れる

素面でも意図したことを忘れがちなしらふいとさんは、忘れる前に何かしら書き残せたらとても満足のようです

fzf.vim による絞り込み対象からファイル名を除外する

先日の記事『重い腰を上げて Neovim ことはじめ ー ripgrep (rg) と fzf でソースコード検索編』で設定した :Rg コマンドはとても便利ですが、使っていて不満な点が一つありました。それは、fzf.vim で絞り込みを行うときにファイルの内容だけでなくファイル名自体も検索対象になってしまうことです。

例えばディレクトリ内のソースコードから foo() という関数を検索するため :Rg コマンドで fzf.vim のインターフェースを立ち上げて foo という文字列でマッチングを行うとします。この際、ディレクトリ内に foo.c というファイルが存在した場合、foo.c の全ての行がその内容に関わらずマッチング結果として表示されてしまいます。

これは rg の出力がファイル名を含んでおり、fzf がマッチングを行う際にファイル名なのかファイルの中身なのかを区別しないことに起因しています。少し調べたところ、同じことで悩んでいる人がおり、解決方法も提示されていました。

解決方法は fzf--delimiter : --nth 3.. というオプションを渡すことです。これは、: をデリミタとして 3 つめのフィールド以降のみをマッチング対象にするというものです。Rg: コマンドで設定している rg の出力フォーマットは ファイル名:行番号:行の中身 となっているため、この設定によってファイル名は絞り込み対象から除外されます。

ということで、該当する箇所の設定はこうなります。

command! -bang -nargs=* Rg
  \ call fzf#vim#grep(
  \   'rg --line-number --no-heading '.shellescape(<q-args>), 0,
  \   fzf#vim#with_preview({'options': '--exact --reverse --delimiter : --nth 3..'}, 'right:50%:wrap'))

Failed to load python (python3) host ー 複数マシンでホームディレクトリを共有している環境ではキャッシュに注意、という話

以前『重い腰を上げて Neovim ことはじめ ー Vim 環境の移行編)』に書いたように Mac では Vim から Neovim へ移行したのですが、この度 Linux でも同様の作業を行ないました。その際につまづいた点があり、事の顛末は ここ にある Neovim の Issue #6414 に書いてありますが、こちらにも記録を残しておきます。

初めに今回の話において重要となる環境について少し書いておきます。複数台の Linux マシンがネットワーク上でホームディレクトリを共有しており、OS は全て Ubuntu 14.04 で、プロセッサはマシンによって異なる世代のものが搭載されている、という構成です。

不思議なことに、あるマシンでは滞りなく Neovim のセットアップが完了するものの、別のマシンでは全く同じ手順でセットアップしたにも関わらず Python の組み込みに失敗する、という現象に遭遇しました。*1


ホームディレクトリを共有しているにも関わらず複数のマシンでそれぞれセットアップするのは、新しいプロセッサのマシンで Linuxbrew を用いてインストールしたバイナリを古いプロセッサのマシンで実行すると悲しいことに “illegal hardware instruction” することがあるためです。そこで Linuxbrew のインストール先は新しいプロセッサのマシンと古いプロセッサのマシンとで異なるディレクトリにしています。今回 Neovim の移行に失敗したのは、古いプロセッサのマシンでした。


具体的には、プラグインを何もインストールしていない素の Neovim で :python print 'hello':python3 print('hello') がどちらもエラーメッセージを出力して失敗します(下記は :python の場合)。*2

function provider#python#Call[9]..remote#host#Require[13]..provider#pythonx#Require, line 15
Vim(if):Channel was closed by the client
Failed to load python host. You can try to see what happened by starting nvim with $NVIM_PYTHON_LOG_FILE set and opening the generated log file. Also, the host stderr is available in messages.

:echo has('python'):echo has('python3') はどちらも 1 を返し(といってもこれらが具体的に何をもって判定しているのかは理解していません)、 :CheckHealthSUCCESS: Latest python2-neovim is installed: 0.1.13 および SUCCESS: Latest python3-neovim is installed: 0.1.13 と言っており、古いバージョンがインストールされているということはなさそうです。

検索してみると Neovim の Issue #2258#4470 では pip install --upgrade --force-reinstall neovim すると治ったという報告があるのですが、残念ながら今回はあてはまりませんでした。

そこで Neovim に Issue をたてて質問してみたところ、python のプロセスにデバッガを attach してみるくらいしかやれることはないよ、と言われたので早速 ここ を参考にやってみました。手順としてはまず NVIM_LISTEN_ADDRESS=/tmp/nvim nvim などとして Neovim を起動します。続けて別のターミナルで python または python3 を実行して、下記のようにコマンドを入力していきます:

Python 3.6.1 (default, Mar 30 2017, 19:12:03)
[GCC 4.8.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from neovim import attach
>>> nvim = attach('socket', path='/tmp/nvim')
[1]    19837 illegal hardware instruction (core dumped)  python3

見事に attach に失敗しました。見てみると、始めの方に書いた illegal hardware instruction (core dumped) というエラーメッセージ。なぜか新しいプロセッサ用にコンパイルされたバイナリを実行してしまっているようです。Linuxbrew でインストールしたバイナリということはないはずなので、pip and/or pip3 でインストールした何かが怪しいのではないかという疑惑。再度 pip install --upgrade --force-reinstall neovim してみると、どうやらこれは再インストールはするものの、再ダウンロードはせずにキャッシュされているものは再利用している模様。つまり、新しいプロセッサのマシンでキャッシュされたものが古いプロセッサのマシンにインストールされてしまったようです。キャッシュディレクトリ(~/.cache/pip)を削除してから再度インストールするか、pip install --no-cache-dir --upgrade --force-reinstall neovim したところ(pip3 も同様)、問題は解決しました。

Neovim のエラーメッセージ Channel was closed by the client は原因についていて言及していないので解決するのに少し時間がかかってしまいました。Linux 環境がやや特殊だったとは思いますが、共有ディレクトリに作成されるキャッシュは思わぬ挙動を引き起こすことがある、ということは頭の片隅に入れておくと良いかもしれません。

*1:Homebrew の代わりに Linuxbrew を用いて 先の記事 のとおりに Neovim への移行作業を行ないました。

*2:$NVIM_PYTHON_LOG_FILE を設定して実行してみましたがログからは原因が分かりませんでした。ログの中身に興味のある方は Issue を見てください。

Bash シェルスクリプトの絶対パスが、そのスクリプトの中からでも分かりますか

分かります。どう書くかを知っていると意外と便利なこの質問は Stack Overflow の こちら です。最も upvote されている回答のワンライナーは下記のとおりです。

"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

ただし回答にも書かれているとおりこのワンライナーには少々問題があり、ファイルがシンボリックリンクだと解決できません(ディレクトリがシンボリックリンクの場合は cdcd -P にすることで解決されます。pwdpwd -P にしても同様だと思います)。回答ではさらにこの問題に対応できる、7行からなるコードも紹介されていますが(興味のある方は こちら)ふだん使いするには少し複雑すぎる気がします。

そこで、コメントにちらほら書かれていたり 紹介されている Gist にもあるように、

"$(dirname "$(readlink -f "$0")")"

がシンプルかつ十分な書き方です。……ただしこのワンライナーにも問題があり、Linux など GNUreadlink がインストールされている環境でしか動きません。

Mac を使っている場合は、たとえば Homebrewcoreutils パッケージをインストールして greadlinkreadlink の代わりに使うか、ファイルのシンボリックリンクの解決は諦めて(ほとんどのユースケースでは事足りると思います)上の BASH_SOURCE を用いた書き方を使うことになるでしょう。

覚えておいて損はないのではないでしょうか。