- はじめに -
Pythonにおけるpython-prompt-toolkit(以下ptk)を使って作られたシェルである「xonsh」を同僚にオススメされて、大体半年くらい使ったので設定とかxontribとか所感を晒していく。
前半でxonshのメリット、デメリットの概要を記載し、後半に自身が利用する設定やxontribについて記載する。
この記事は、xonsh導入に至る人もしくは、環境設定について広く知りxonshを扱える人を増やす事が目的である。
- xonshについて -
xonshはPythonのptkを用いて作成されたShellである。
Pythonで書かれたシェルで代表的な物としてfishがあるが、「fishに比べ、よりPythonに寄ったシェル」という位置付けになる。
読み方についてはゾンシュ、カンク、コンク、エックスオンエスエッチ等諸説あるが、公式ページでランダムに表示される文言の中に [コンシュ] が入っている事からコンシュが正解であると思われる。
xonshの良さ
筆者は、何周か回って「xonshの良さ」は端的に以下に集約されると考えている。
1の「python資産の活用とワンライナーシェルコマンドからの開放」は最も筆者に得を与えている。「シェル芸」と呼ばれる技術は、テクニカルで高速で最も我々の生活に馴染んでいるものであるが、時に複雑な処理を書きたい場合において人類にはやや理解し難い構文が発生してしまう。何より、各コマンドを中度以上に知る人間が読まなければ、何をどういう原理で処理しているのかワンライナーの理解でさえ時間を要する。
以下の例のように、既存のシェルコマンドの入出力を利用しながら、Pythonで行いたい処理に流したり、逆流する事も可能である。もちろん、これらを関数としてコマンド化する事もxonshであれば簡易に行える。
# lsコマンドの結果を利用しながらpythonでの処理を行う例 for x in ![ls ~/work/]: print(x) # > hoge.txt # > piyo.md # > .... # pythonスクリプトの結果をシェルコマンドに流す例 import sys echo @(sys.version_info) | sed 's/ /\\\x0A/g' # > 3 # > 6 # > 3 # > candidate # > 1
日々Pythonを書き慣れている人間であれば、manやhelpを読み込む必要無く複雑な処理を記述し、スクリプトやコマンドとして再利用する事ができるであろう。
2の「defaultで利用可能なリッチな補完と履歴」はxonshの魅力の1つである。コマンドの補完は履歴からhistoryからの補完やMan-pageを参照した補完が(ptkがinstallされていれば)defaultで扱える。ちなみにこの補完は、fishを参考に作られている。
3の「中身も全てPythonでOSに依存しない高速な環境構築が簡易」が筆者が最もxonshを多く利用している理由である。永らくzshを利用してきた筆者だが、様々な管理ソフトウェア、パッケージをインストールして利用するため、環境構築スクリプトは幾度もの修正によって異様な形相となっている。oh-my-zsh、Antigen、prezto、zplugを経てこそ短くなったrcファイルも、管理できているとは言えないようなものだ。
xonshは、本体がpythonパッケージ管理であるpipでインストールできるだけでなく、拡張となるxontribもpipで導入できる。「サーバにDockerは導入できないがpythonは入っている」といった状態であれば、作業用Dcokerコンテナが無くともpipであらかたの環境が整う。これは、初学者にも効能があると考えていて、簡易な導入で高度な機能が使えるのCLIというのは魅力になり得る。
また、シェル自体もPythonである故、コマンドや動作自体のOverrideもPython、configもrc fileもPythonで記述できる。Pythonを習熟していれば、CLIを習熟できるようなものだ(言い過ぎ)。
余談だが、bashやfishとの比較に関しては以下のツイートが結構すき。
メイン開発者のscopatzは強めの比較がすきっぽい。
まあ http://xon.sh/#comparison の比較もなかなかパワフルだしそういうものか。
xonshのまだまだなところ
xonshがまだまだな点も同様に挙げておく。
1つに、完全にbash互換ではない事が挙げられる。Pythonとの競合部分故、権限的に触れない部分があったりOSコマンドやブレース展開は正しく扱えない場合がある。Hacker News内のXonsh, a Python-ish, Bash-compatible shell language and command promptでも、fishのリードエンジニアとxonshの作者が「bash互換と言うのはやめた方がええんちゃうか」と議論しており、完全なPOSIXサポートシェルではなく「Pythonが扱えるシェル初心者であれば、気軽にシェル操作が行える」辺りを目指しているようだ。
そして「たまに落ちる」。普段の作業中に落ちる事はほぼ無いのだが、設定書き換えてロードした直後や、xonsh上でゴリゴリにメモリを使うと落ちる。同僚はxonshを数週間使い続けるとPC全体が重くなってしまうとも言っており、Pythonのガベージコレクタとの戦いがたまに見られる。後述するが、筆者は簡易に設定したzshの上でxonshを走らせ、いつ死んでも良いようにしている。
ガベージコレクタも理由の1つだが、Pythonシェルが生理的に無理という人にはオススメできない。ただ、こればかりはどうしようもない。Pythonはそもそもシェル向きの言語かと聞かれれば筆者も口を噤むし、Pythonじゃなくて〇〇だったら?と聞かれれば簡単に心が揺れるだろう。コマンドシェル - ArchWikiですら、xonshはレトロなシェルとして紹介されているし、もうすまんそれならzshを使ってくれという感じである。
- 筆者のxonsh環境 -
以降は、筆者が利用しているxonsh環境について記載する。
2018/06/22、xonsh 0.6.1 時点であり、絶対最強という訳でもないと思う。
zshの上で動かす
これ大事。xonshが死んで作業できなくなったら終わり。
もちろんbashでも良い。
色々試した結果、~/.zshrcの最後にxonsh起動をコマンド書いてデフォルトシェルをzshにする形に落ち着いた。起動が若干遅くなるが、どうせシェルは常時起動しているので心を広く保つ事でカバーしている。
Mac向けの設定なのでPATHはよしなに。bashだとalias以降は動くはず。
# 人類最低限zshrc autoload -U compinit; compinit setopt auto_cd setopt auto_pushd setopt pushd_ignore_dups setopt histignorealldups setopt always_last_prompt setopt complete_in_word setopt IGNOREEOF export LANG=ja_JP.UTF-8 autoload -Uz colors colors alias l='ls -ltr --color=auto' alias ls='ls --color=auto' alias la='ls -la --color=auto' PROMPT="%(?.%{${fg[red]}%}.%{${fg[red]}%})%n${reset_color}@${fg[blue]}%m${reset_color} %~ %# " zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}' # vim export VISUAL='/usr/local/bin/vim' # pyenv export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" # gcloud if [ -f '/Users/shukawai/google-cloud-sdk/path.zsh.inc' ]; then source '/Users/shukawai/google-cloud-sdk/path.zsh.inc'; fi if [ -f '/Users/shukawai/google-cloud-sdk/completion.zsh.inc' ]; then source '/Users/shukawai/google-cloud-sdk/completion.zsh.inc'; fi # xonsh起動 alias x='xonsh' x
自分が作業していく中で見たxonshから利用しづらいものとしてpyenv、gcloudコマンドがある。
こればかりは仕方ないのでzshで操作するようにしている。
また、LD_LIBRARY_PATHやPATHもxonshrcではなく上層のシェルで書いておかないと動かなかったりする(動く時もある)。
$PATH.append("/usr/local/bin")のようにxonshrcに記載するのが正解か。
なんか「他にこれもダメだった」というのがあれば、はてブかTwitterにでも書いてくれればエゴサします。
xonshrc
設定ファイル。~/.xonshrcに書く。
hoge.xshファイルを作って~/.xonshrc内でfrom hoge import *としてやる事でファイル分割もできる。
~/.config/xonsh/rc.xshでも多分大丈夫。
最低限な部分だけ以下に抜粋。
# -*- coding: utf-8 -*- # エディタ $EDITOR = '/usr/local/bin/vim' $VISUAL = '/usr/local/bin/vim' # vi風の操作がシェル上で直感的でないのでFalse $VI_MODE = False # 補完をEnterで直接実行しない $COMPLETIONS_CONFIRM = True # Ctrl + D で終了しない $IGNOREEOF = True # tabではなく空白4つ $INDENT = " " # 補完時に大小区別しない $CASE_SENSITIVE_COMPLETIONS = False # 連続重複コマンドを保存しない $HISTCONTROL = "ignoredups" # 括弧を補完 $XONSH_AUTOPAIR = True # ディレクトリ名を入力でcd $AUTO_CD = True # エラー全て吐くように $XONSH_SHOW_TRACEBACK = True # サブプロセスタイムアウトのメッセージ抑制 $SUPPRESS_BRANCH_TIMEOUT_MESSAGE = True # キー入力即評価(サイコー) $UPDATE_COMPLETIONS_ON_KEYPRESS = True # プロンプトの表記 $PROMPT = "{INTENSE_RED}{user}{INTENSE_GREEN}@{INTENSE_BLUE}{hostname}{INTENSE_YELLOW} [ {cwd} ] {GREEN}$ " # lsコマンドの結果の見た目 $LS_COLORS="di=34:ln=35:so=32:pi=33:ex=31:bd=46;34:cd=43;34:su=41;30:sg=46;30:tw=42;30:ow=43;30" # alias # Macではcolorlsを導入しているためplatform.systemを利用して判別 import platform if platform.system() == 'Darwin': aliases["ls"] = "ls -G" aliases["lc"] = "colorls --sf" aliases["lt"] = "colorls --tree" aliases["l"] = "colorls -ltr --sf" aliases["la"] = "colorls -la" aliases["ll"] = "colorls -l" aliases["lx"] = "colorls -x" aliases["lf"] = "colorls -f" aliases["ld"] = "colorls -d" else: aliases['ls'] = "ls --color=auto" aliases["l"] = "ls -l" aliases["la"] = "ls -la" aliases["ll"] = "ls -l" aliases["v"] = "vim" aliases["vi"] = "vim" aliases["vx"] = "vim ~/.xonshrc" aliases["vz"] = "vim ~/.zshrc" aliases["vv"] = "vim ~/.vimrc" aliases["vs"] = "vim ~/.ssh/config" # コマンド入力中にcmd+vでvim編集 from prompt_toolkit.keys import Keys from prompt_toolkit.filters import (Condition, IsMultiline, HasSelection, EmacsInsertMode, ViInsertMode) @events.on_ptk_create def custom_keybindings(bindings, **kw): handler = bindings.registry.add_binding @handler(Keys.ControlV) def edit_in_editor(event): event.current_buffer.tempfile_suffix = '.py' event.current_buffer.open_in_editor(event.cli) # 直近のxonshjobころすマン # https://github.com/zardus/xonshrc/blob/master/xonshrc def _kill_last(args, stdin=None): if __xonsh_active_job__ is None: print("No active job. Aborting.") return cmd = 'kill %s %d' % (''.join(args), __xonsh_all_jobs__[__xonsh_active_job__]['pgrp']) os.system(cmd) aliases['kill_last'] = _kill_last # free # https://github.com/asmeurer/dotfiles/blob/master/.xonshrc def free(args, stdin=None): disk_info = $(diskutil info /) return [i for i in disk_info.splitlines() if "Free" in i][0] + '\n' aliases['free'] = free # gc import gc def _gc(args, stdin=None): gc.collect() aliases['gc'] = _gc # ライブラリの実行時import # https://vaaaaaanquish.hatenablog.com/entry/2017/12/26/190153 # xonsh上で使うときがありそうなライブラリはlazyasdで実行時読み込み from xonsh.lazyasd import lazyobject import importlib lazy_module_list = ["requests", "numpy", "pandas", "matplotlib"] for x in lazy_module_list: t = "@lazyobject\ndef {}():\n return importlib.import_module('{}')".format(x, x) exec(t) # ライブラリのバックグラウンドimport # https://vaaaaaanquish.hatenablog.com/entry/2017/12/26/190153 # 絶対使うやつは別スレッドで読み込み from lazyasd import load_module_in_background background_module_list = ["os", "sys", "random", "shutil", "linecache"] for x in background_module_list: exec("{}=load_module_in_background('{}')".format(x, x))
設定可能な環境変数値は日本語含めて以下にまとめてあるが、ver6.0の時に書いたものなので、公式の http://xon.sh/envvars.html をウォッチしておくと良い。
vaaaaaanquish.hatenablog.com
またGithubでxonshrcで検索する他、Xonsh Advent Calendar 2017 - Qiita でも設定周りの記事を書いている人がいるので参考になると思います。xonshrc書く上でのオススメは以下辺り。
qiita.com
qiita.com
また手前味噌ですが、xonshの各種eventについても書いてます。
vaaaaaanquish.hatenablog.com
またまた手前味噌ですが、自分もxonshの記事結構書いています。PROMPTに画像出したりdatetime出したり、対話的な選択コマンド作ったり、matplotlibで遊んだりしています。
xonsh カテゴリーの記事一覧 - Stimulator
xontrib
xonshを使う上で楽しい拡張について記載する。
基本的には以下にまとめています。
vaaaaaanquish.hatenablog.com
自分が使っている物だけ抜粋。
# xonshrc - xontrib # Docker周りの補完 pip install xonsh-docker-tabcomplete xontrib load docker_tabcomplete # zコマンドの利用 pip install xontrib-z xontrib load z # fzfコマンドの利用 pip install xontrib-fzf-widgets xontrib load fzf-widgets # tracebackを省略し見やすくする pip install xontrib-readable-traceback xontrib load readable-traceback $READABLE_TRACE_STRIP_PATH_ENV=True $READABLE_TRACE_REVERSE=True
基本的にdockerやz、fzfが既に動作する環境では上記を利用している。
別途それらをインストールしているという意。
fzfは環境によって日本語文字化けが発生する事があり、pecoを利用している。
履歴周りをfzfやpecoに流す時のヒントは以下。
qiita.com
最後のreadable-tracebackは手前味噌ですが、筆者が作っています。
GitHub - vaaaaanquish/xontrib-readable-traceback: Make traceback easier to see for xonsh.
config.json
より外側の設定ファイル。~/.config/xonsh/config.jsonに書く。
一応以下の記事にまとめてあるが、config.jsonはサポートされなくなり、xonshrcになったので不要。
Xonshのconfigを書く - Stimulator
WARNING! old style configuration (/Users/xxx/.config/xonsh/config.json) is no longer supported. Please migrate to xonshrc.
基本的な操作と編集
xonsh独特なやつだと補完候補を確定させたい時はctrl+e、ctrl+↑↓で複数行のコマンドまたいで履歴移動、複数行にまたがるコマンドを途中で実行したければesc, enterくらい覚えておけば良さそう。ctrl+cでコマンドキャンセル、ctrl+a,eで行頭尾に移動とか、ctrl+←→で移動とか基本的なやつは身体で感じて覚えるかカスタマイズする。
複数行の記述は基本vimでやっている。xonshrcでconsole入力中にctrl+vでvim呼び出せるようにしており、vimで複数行コマンドを編集、quitすると入力されている状態となる。
また、以下のようにvimrcを設定し、SyntaxHighlightや補完をxonsh周りのファイルでも効くようにしておけば便利。
" vimrc autocmd BufRead,BufNewFile *.xonshrc setfiletype python autocmd BufRead,BufNewFile *.xsh setfiletype python
検索はctrl+rだが、peco等に流せるなら設定しておくと便利。
おわりに
xonshのメリット、デメリットの概要と、自身が利用する設定やxontribについて記載した。
Pythonは(無理な所ももちろんあるが)生理的に嫌いという程ではないので、できるだけ多くの人にxonsh使って欲しいし、みんなxontrib作って公開して欲しい。
ブログも書いて欲しい。
いやほんとマジで。