nushellを試してみる

[nushell](https://www.nushell.sh/)はrust製のクロスプラットフォームなシェルで，特徴として全ての出力に型が付いていることがあげられます．wingetやchocolateyなどのパッケージマネージャーを用いて[インストールできます](https://www.nushell.sh/book/installation.html#package-managers)．

## コマンドの利用
コマンドはnushellで独自に定義されており，パイプラインを用いて次々と処理していくことができます．例えば
```
〉ls
╭───┬────────────┬──────┬───────┬──────────────╮
│ # │    name    │ type │ size  │   modified   │
├───┼────────────┼──────┼───────┼──────────────┤
│ 0 │ .gitignore │ file │   8 B │ 18 hours ago │
│ 1 │ Cargo.lock │ file │ 156 B │ 18 hours ago │
│ 2 │ Cargo.toml │ file │ 181 B │ 18 hours ago │
│ 3 │ src        │ dir  │   0 B │ 18 hours ago │
│ 4 │ target     │ dir  │   0 B │ 18 hours ago │
│ 5 │ utils.nu   │ file │ 956 B │ 2 hours ago  │
╰───┴────────────┴──────┴───────┴──────────────╯
```

のような出力に対してインデックスを指定することでファイルの情報を取得できます．これは`ls`がテーブルを返すためです．
```
〉ls | get 2 
╭──────────┬──────────────╮
│ name     │ Cargo.toml   │
│ type     │ file         │
│ size     │ 181 B        │
│ modified │ 18 hours ago │
╰──────────┴──────────────╯
```

コマンドの結果を別のコマンドの引数として利用する場合は`()`でくくります．以下の例では`ls`で取得した0番目のディレクトリである`src`に移動しています．

```
〉ls | where type == dir
╭───┬────────┬──────┬──────┬──────────────╮
│ # │  name  │ type │ size │   modified   │
├───┼────────┼──────┼──────┼──────────────┤
│ 0 │ src    │ dir  │  0 B │ 18 hours ago │
│ 1 │ target │ dir  │  0 B │ 18 hours ago │
╰───┴────────┴──────┴──────┴──────────────╯
〉cd (ls | where type == dir | get 0.name)
```

## 外部コマンドと併用
`()`を用いることで外部コマンドの引数としても結果を利用できます．例えば`cargo run`コマンドで以下の`src/bin`ディレクトリにある`hoge.rs`をコンパイルして実行したい時，通常は`cargo run --bin hoge`としますが
```
.
├ src
│ ├ main.rs
│ └ bin
│ 　 ├ fuga.py
│ 　 ├ hoge.rs
│ 　 └ piyo.rs
├ target
├ Cargo.toml
└ Cargo.lock
```
```rust:src/bin/hoge.rs
fn main() {
    println!("Hello, world! hoge");
}
```

以下のようにすることで`ls`の結果の番号で指定できます．

```
〉ls src/bin
╭───┬─────────────────┬──────┬──────┬──────────────╮
│ # │      name       │ type │ size │   modified   │
├───┼─────────────────┼──────┼──────┼──────────────┤
│ 0 │ src\bin\fuga.py │ file │  0 B │ 3 hours ago  │
│ 1 │ src\bin\hoge.rs │ file │ 53 B │ 19 hours ago │
│ 2 │ src\bin\piyo.rs │ file │ 54 B │ 3 hours ago  │
╰───┴─────────────────┴──────┴──────┴──────────────╯

〉cargo run --bin (ls src/bin | get 1.name | path parse | get stem)
Hello, world! hoge
```
先ほどの処理に加えて パスを構造体(レコード)に変換，`stem`を取得しています．

## カスタムコマンドの定義
カスタムコマンドはシェル上でも定義できますが，今回はファイルで定義します．`ls`のうち`.rs`ファイルのみを返すコマンド`ls-rs`は以下のように定義できます．
```nu:./utils.nu
export def ls-rs [pattern?] {
    if ($pattern == null) {
        ls | where name ends-with ".rs"
    } else {
        ls $pattern | where name ends-with ".rs"
    }
}
```
引数や変数として利用する場合は変数名に`$`を付けます．また`pattern?`のように`?`を付けることでオプションの引数を定義でき，引数が渡されなかった場合は値が`null`あるは`$nothing`となります．引数の型は指定しない場合`any`になりますが，今回は`glob`で指定しても大丈夫です．

定義したコマンドをシェルで利用する場合は`source utils.nu`とします．
```
〉source utils.nu
〉ls-rs src/bin
╭───┬─────────────────┬──────┬──────┬──────────────╮
│ # │      name       │ type │ size │   modified   │
├───┼─────────────────┼──────┼──────┼──────────────┤
│ 0 │ src\bin\hoge.rs │ file │ 53 B │ 19 hours ago │
│ 1 │ src\bin\piyo.rs │ file │ 54 B │ 3 hours ago  │
╰───┴─────────────────┴──────┴──────┴──────────────╯
```

単に`.nu`ファイルを実行する場合は`nu utils.nu`としますが，`source`を用いることでファイルで定義したコマンドや環境変数がシェルでも維持されます．また`use`を用いることでも利用が可能です．

```
〉use utils.nu 
〉utils ls-rs src/bin
╭───┬─────────────────┬──────┬──────┬──────────────╮
│ # │      name       │ type │ size │   modified   │
├───┼─────────────────┼──────┼──────┼──────────────┤
│ 0 │ src\bin\hoge.rs │ file │ 53 B │ 19 hours ago │
│ 1 │ src\bin\piyo.rs │ file │ 54 B │ 3 hours ago  │
╰───┴─────────────────┴──────┴──────┴──────────────╯
```

`use`はファイルをモジュールとして利用するため，ファイル内でコマンドの実行や環境変数への代入はできずコマンドやエイリアスなどの定義のみ可能です．また`use`でインポートするコマンドには`export`を付ける必要があり，付けないものはファイルのローカルのコマンドとなります．

次に`cargo run`で用いたコマンドをカスタムコマンドとして定義してみます．先ほどのコマンドの定義とは異なり`""`で囲むことでスペースの空いたサブコマンドとして定義できます．ここでは引数`pattern`にデフォルト値を与えています．そして`stem`を`echo`するフラッグも引数としています．引数の横のコメントは`help`コマンドや`-h`フラッグを使ったときに表示されます．

```nu:./utils.nu
export def "stem ls" [
    number: int   # Row number
    pattern="./"  # The glob pattern to use
    --echo  # Echo the stem string
] {
    let stem_str = ( ls $pattern | get $number | get name | path parse | get stem)
    if $echo { echo $stem_str }
    $stem_str
}
```

これで以下のように`cargo run`が使えます．

```
〉ls src/bin
╭───┬─────────────────┬──────┬──────┬──────────────╮
│ # │      name       │ type │ size │   modified   │
├───┼─────────────────┼──────┼──────┼──────────────┤
│ 0 │ src\bin\fuga.py │ file │  0 B │ 4 hours ago  │
│ 1 │ src\bin\hoge.rs │ file │ 53 B │ 20 hours ago │
│ 2 │ src\bin\piyo.rs │ file │ 54 B │ 3 hours ago  │
╰───┴─────────────────┴──────┴──────┴──────────────╯
〉cargo run --bin (stem ls 1 src/bin)
Hello, world! hoge
```

これでもいいのですが，オプション引数を後ろに持ってくるために引数の順番が直感的では無かったり，`ls src/bin | sort-by name | stem 1`のようにパイプラインで用いることができません．
nushellではパイプラインで渡された値を`$in`で利用できるため，以下のように改良できます．

```nu:./utils.nu
export def stem [
    number: int  # Row number
    --echo  # Echo the stem string
] {
    let stem_str = ($in | get $number | get name | path parse | get stem)
    if $echo {echo $stem_str}
    $stem_str
}
```

以下のようにパイプラインで利用できます．

```
〉ls src/bin | sort-by modified
╭───┬─────────────────┬──────┬──────┬──────────────╮
│ # │      name       │ type │ size │   modified   │
├───┼─────────────────┼──────┼──────┼──────────────┤
│ 0 │ src\bin\hoge.rs │ file │ 53 B │ 20 hours ago │
│ 1 │ src\bin\fuga.py │ file │  0 B │ 5 hours ago  │
│ 2 │ src\bin\piyo.rs │ file │ 54 B │ 4 hours ago  │
╰───┴─────────────────┴──────┴──────┴──────────────╯
〉cargo run --bin (ls src/bin | sort-by modified | stem 0 --echo) 
hoge
Hello, world! hoge
```

## シェルの起動時に読み込む
シェルの起動時に読み込むファイルとして`env.nu`と`config.nu`があり，それぞれ`$nu.env-path`と`$nu.config-path`でそのパスを出力できます．その`config.nu`に先ほど作成した`utils.nu`を読み込ませれば，`ls-rs`や`stem`をどこからでも利用できるようになります．
具体的には，`config.nu`と同じディレクトリに`utils.nu` を移動し以下を追記するだけです．
```nu:config.nu
source utils.nu
```

他にもpythonにおける仮想環境のような仕組みであるoverlaysもあります．