Rust製のシェル Nu Shell を試してみた

しばたです。
ブログのネタ探しのためにネットサーフィンをしていたところRust製のNu Shellと呼ばれるシェルがある事を知りました。

作者によるイントロダクションは以下。

こちらのシェルは

A modern shell for the GitHub era

とあり、組み込みでGit連携できたりと新しめの機能を導入しているシェルなのですが、私が特に目を引いたのが、

(https://github.com/nushell/nushell より)

Nu draws inspiration from projects like PowerShell, functional programming languages, and modern cli tools.

の一文で、PowerShellおじさんとしては「これは試さなければならぬ!」という気持ちになった次第です。

Nu Shellのステータス

このNu Shellですが、GitHubの説明に

This project has reached a minimum-viable product level of quality.

とある様に現時点では最小限の機能のみ実装された状態で、バージョンとしては本日時点でVer.0.2.0となります。

検証環境

ここからは実際にNu Shellを試してPowerShellとの比較をしてみたいと思います。
検証環境としては私の開発機である64bit版Windows 10 May 2019 Update(1903)を使いました。

インストール方法

GitHub上にWindows向けのバイナリが公開されていますので、ダウンロードして適当なディレクトリに展開します。
現在のバージョンはVer.0.2.0です。

今回はC:\Program Files2\nushellフォルダに展開しています。

試してみた

展開したファイルのうち、nu.exeがシェル本体ですのでダブルクリックして起動します。

特に初期処理などは無くいきなりCUIコンソールが表示されます。
基本的にはコマンドプロンプトと互換がある様でvermklinkといったコマンドプロンプトの内部コマンドが使えます。

ここからはNu Shell独自の機能を試していきます。

Git連携

Nu Shellは標準でGit連携する様になっており、Gitでソース管理しているディレクトリに移動すると現在のブランチを表示してくれます。

git-prompt.shposh-gitにあるアレですね。
残念ながら現時点ではファイルの更新状況を表示したり、gitコマンドに対するCompleterといった機能はない様です。

内部コマンドとオブジェクト

次にNu Shellではlsといった独自の内部コマンドを持ち、この内部コマンドはPowerShellの様に型を持ったデータを返します。
たとえば、以下の様に

# Nu Shellにおいて ls は内部コマンド
ls 

とだけ打つと、下図の様に表形式でディレクトリ・ファイル情報が表示されますが、この実体はテキストではなくオブジェクトです。

このオブジェクトはPowerShellと同じ様にパイプラインで操作することが可能です。
たとえば

ls | where type == Directory

とするとディレクトリだけを抽出、

さらに

ls | where type == Directory | pick name

とするとディレクトリのNameカラムを抽出することができます。

PowerShellに親しんでいる人間からすると非常に馴染みのある感じです。
ちなみにPowerShellで同様のことをする場合は、

dir | where { $_.PsIsContainer } | Select-Object Name

といった感じのコードになります。

内部コマンドの一覧

現在ある内部コマンドの一覧はGitHubをご覧ください。

PowerShellのGet-Commandに相当する内部コマンドを検索するコマンドはまだ無い様です。

オブジェクトの一覧

PowerShellは.NET Framework/.NET Core製で全てが.NETの型を持つオブジェクトから構成されていますが、Nu Shellでは幾つかのプリミティブな型と構造化データとなるObjectListでオブジェクトは構成されています。
オブジェクトの一覧は以下のドキュメントに記載されており、

プリミティブな型は

内容 備考
Integer 整数 ソースを見る限り内部的にはi64
Float 小数 ソース上見る限り内部表記および内部型はDecimal
String 文字列
Boolean 真偽値
Date 日付・時刻 タイムゾーンを内包する
Path ファイルパス ソースを見る限り内部的にはPathBuf
Byte kb、mbなどの単位を持った整数 ソースを見る限り内部的にはu64

構造化データは

内容 備考
Objects 子要素を持つオブジェクト型 内部的にはDictionaryらしい
Binary バイナリデータ 内部的にはVec<u8>
List リスト型 すべてがListでArrayは無い様にみえる
Block コードブロック

が定義されています。
ちなみにソース的には以下の様に定義されていました。

pub enum Primitive {
    Nothing,
    Int(i64),
    #[allow(unused)]
    Float(OF64),
    Bytes(u64),
    String(String),
    Boolean(bool),
    Date(DateTime<Utc>),
    Path(PathBuf),

    // Stream markers (used as bookend markers rather than actual values)
    BeginningOfStream,
    EndOfStream,
}

// ・・・中略・・・

pub enum Value {
    Primitive(Primitive),
    Object(crate::object::Dictionary),
    #[serde(with = "serde_bytes")]
    Binary(Vec<u8>),
    List(Vec<Tagged<Value>>),
    #[allow(unused)]
    Block(Block),
}

私はRustは全然できませんが、これだけでもなんとなくオブジェクトの実体の予想はつくかと思います。

パイプライン

Nu Shellではパイプラインの扱いについて以下のドキュメントにまとめられており、

コマンドの組み合わせ方による挙動が定義されています。

  1. 内部コマンド | 外部コマンドでパイプする場合
    • 内部コマンドで発生した"Data"(オブジェクト)は文字列として標準出力に出力される。
  2. 外部コマンド | 内部コマンドでパイプする場合
    • 外部コマンドの出力は"1つ"の文字列として内部コマンドに渡される
  3. 外部コマンド1 | 外部コマンド2でパイプする場合
    • 外部コマンド1の標準出力と外部コマンド2の標準入力を接続し直接データをパイプさせる

この挙動をPowerShellと比較すると3.のパターンの挙動が大きく異なります。
PowerShellの場合は外部コマンド1 | 外部コマンド2でパイプする場合でもストリーム介することになり、外部コマンド1 -> "ストリーム" -> 標準出力 -> 標準入力 -> 外部コマンド2という経路でデータが流れてしまいます。
このためPowerShellにおいては外部コマンド同士のパイプラインがユーザーの期待した動作にならないことが非常に多い現状があります。
PowerShell Core 6.0で若干改善されてはいるのですが根本的な挙動に変わりはなく、この外部コマンド同士のパイプはPowerShellにおける大きな弱点なのですが、Nu Shellではこの部分はうまく対処されている感じです。

Shells

Nu Shell独自の機能としてShellsというものがあります。
enterコマンドで新しいシェルを立ち上げ、いわゆる"screen"コマンドの代替となる様な機能なのですが、個人的にはあまりうま味がわかりませんでした...
shellsコマンドで現在立ち上がっているシェルの一覧を参照できます。

シェルの切り替えはpnコマンド、シェルを終了するときはexitコマンドを使います。

Plugins

今回は試しませんでしたがNu Shellはプラグイン機構を持ち独自の機能を追加できる様になっているそうです。
仕様としてはnu_plugin_[コマンド名]といった名前の実行ファイルを作れば良いそうで、GitHubのpluginsフォルダにある実装を参考に作れるとのことです。

最後に

以上、簡単ですがNu Shellを触ってみた感想となります。

PowerShellは 全て が.NETのオブジェクトから構成されており、それがPowerShellの強みであるものの独特のクセや外部コマンドとの連携の弱さとなっている部分があります。
Nu Shellではオブジェクトの使用は限定的・選択的であり、オブジェクトを使うところを都度考えないといけない面倒さはありますが、その分クセは少なく外部コマンドとの連携もしやすいのかなと思いました。

最初に説明したとおり出来たばかりのシェルで機能的に不足している部分はまだまだありますが今後の発展に期待していきたい感じです。