Nushell が Elixir っぽくってワクワクした

## はじめに

私は寡聞にして存じなかったのですが、 先日 Nushell なるものを教えてもらいました

https://www.nushell.sh/ja/

Qiita にも既にいくつも記事があるし、 2019 年からあるものなので今更感が強いですが、使ってみると何とも Elixir で、アルケミスト的にワクワクしたので記事にしておきます

（Rust 製だけど）

## インストール

macOS の場合、 [Homebrew](https://brew.sh/index_ja) でインストールできます

```shell
brew install nushell
```

Windows の場合は [winget](https://learn.microsoft.com/ja-jp/windows/package-manager/winget/) です

```shell
winget install nushell
```

ログインシェルとして設定する方法も記載されていますが、推奨はされていません

https://www.nushell.sh/ja/book/installation.html#%E3%83%AD%E3%82%AF%E3%82%99%E3%82%A4%E3%83%B3%E3%82%B7%E3%82%A7%E3%83%AB%E3%81%A8%E3%81%97%E3%81%A6%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B%E3%81%AB%E3%81%AF

## Nushell の開始

お使いのターミナル（[iTerm2](https://iterm2.com/) や [warp](https://www.warp.dev/) や [Windows Terminal](https://apps.microsoft.com/store/detail/windows-terminal/9N0DX20HK701?hl=ja-jp&gl=jp)）から `nu` を実行しましょう

```shell
nu
```

![スクリーンショット 2023-03-18 11.42.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/833699dd-9740-669b-1c81-841493a30377.png)

すると Nushell の [Discord](https://discord.gg/NtAbbGn) や [GitHub](https://github.com/nushell/nushell) などなどが表示されて Nushell の世界に入ります

## Nushell の基本

### ファイル一覧

まずファイル一覧を出してみましょう

```shell
ls
```

![スクリーンショット 2023-03-18 11.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/13a551a7-61d4-9e18-d130-2576cc68b66b.png)

特にオプション等指定せずとも良い感じにテーブル表示してくれます

ではパイプラインを使ってみましょう

:::note info
以降、画像ではなくテキストで実行結果を貼り付けます

[容量制限が怖いので](https://qiita.com/RyoWakabayashi/items/3de067e1b1b5243a3762)
:::

```shell
$ ls | sort-by size | reverse
╭───┬─────────┬──────┬──────────┬───────────────╮
│ # │  name   │ type │   size   │   modified    │
├───┼─────────┼──────┼──────────┼───────────────┤
│ 0 │ 002.txt │ file │   3.0 MB │ 6 minutes ago │
│ 1 │ 003.txt │ file │ 608.3 KB │ 8 minutes ago │
│ 2 │ 011.txt │ file │   1.7 KB │ 8 minutes ago │
│ 3 │ yyy     │ dir  │    160 B │ 3 minutes ago │
│ 4 │ xxx     │ dir  │    128 B │ 3 minutes ago │
│ 5 │ 001.txt │ file │     16 B │ 9 minutes ago │
╰───┴─────────┴──────┴──────────┴───────────────╯
```

「ファイル一覧を取得して」 `|` 「サイズ順に並び替えて」 `|` 「順序を逆にする」というのが直感的にわかりますね

通常の bash だと以下のようになります

```shell
$ ls -lhS
total 7608
-rw-r--r--  1 oec  staff   2.9M  3 18 11:50 002.txt
-rw-r--r--  1 oec  staff   594K  3 18 11:48 003.txt
-rw-r--r--  1 oec  staff   1.7K  3 18 11:48 011.txt
drwxr-xr-x  5 oec  staff   160B  3 18 11:53 yyy
drwxr-xr-x  4 oec  staff   128B  3 18 11:53 xxx
-rw-r--r--  1 oec  staff    16B  3 18 11:47 001.txt
```

「[短いことこそが価値である場合](https://code.golf/)」には良いですが、オプションを覚えていないと全く理解できませんね

ちなみにファイルサイズが違うのは、 1,024 で割っているか 1,000 で割っているかの違いです

1,024 で割った方（bash の表現）は厳密には KiB （キビバイト）ですね

続いてフィルターです

```shell
$ ls | where size > 1kb
╭───┬─────────┬──────┬──────────┬────────────────╮
│ # │  name   │ type │   size   │    modified    │
├───┼─────────┼──────┼──────────┼────────────────┤
│ 0 │ 002.txt │ file │   3.0 MB │ 21 minutes ago │
│ 1 │ 003.txt │ file │ 608.3 KB │ 23 minutes ago │
│ 2 │ 011.txt │ file │   1.7 KB │ 22 minutes ago │
╰───┴─────────┴──────┴──────────┴────────────────╯
```

サイズが 1 KBより大きいものを抽出する、というのが読み取れます

ちなみに bash だと以下のようになります

```shell
$ find . -maxdepth 1 -type f -size +1k | xargs ls -ldh
-rw-r--r--  1 oec  staff   2.9M  3 18 11:50 ./002.txt
-rw-r--r--  1 oec  staff   594K  3 18 11:48 ./003.txt
-rw-r--r--  1 oec  staff   1.7K  3 18 11:48 ./011.txt
```

最早読めというのが無茶だし、ググらずに書くのは私には不可能です

### プロセス一覧

プロセスの一覧を表示したり、特に重い処理を探したりするのも楽チンです

```shell
$ ps | where cpu > 5
╭───┬───────┬────────────────────┬─────────┬───────┬──────────┬─────────╮
│ # │  pid  │        name        │ status  │  cpu  │   mem    │ virtual │
├───┼───────┼────────────────────┼─────────┼───────┼──────────┼─────────┤
│ 0 │ 75346 │ nu                 │ Running │  9.87 │  13.3 MB │ 35.0 GB │
│ 1 │ 61476 │ iTerm2             │ Sleep   │ 15.09 │ 125.8 MB │ 35.7 GB │
│ 2 │  3572 │ qemu-system-x86_64 │ Sleep   │  8.69 │ 481.2 MB │ 48.2 GB │
╰───┴───────┴────────────────────┴─────────┴───────┴──────────┴─────────╯
```

### 現在時刻の取得

現在時刻も分かりやすく `date now` で取得できます

```shell
$ date now
Sat, 18 Mar 2023 12:23:16 +0900 (now)
```

### システム情報の取得

`sys` で OS などの情報を取得することができます

これは実行結果が大きすぎるため実際の実行結果は貼り付けませんが、以下のような形式になっています

```shell
╭───────┬───────────────────╮
│ host  │ {record 6 fields} │
│ cpu   │ [table 4 rows]    │
│ disks │ [table 3 rows]    │
│ mem   │ {record 4 fields} │
│ temp  │ [table 1 row]     │
│ net   │ [table 4 rows]    │
╰───────┴───────────────────╯
```

つまり、 Elixir でいうところの Map になっています

では host のところだけ取り出してみます

```shell
$ sys | get host
╭─────────────────┬───────────────────────────╮
│ name            │ Darwin                    │
│ os_version      │ 13.2.1                    │
│ long_os_version │ MacOS 13.2.1              │
│ kernel_version  │ 22.3.0                    │
│ hostname        │ xxxxx                     │
│ uptime          │ 1wk 1day 13hr 56min 51sec │
│ boot_time       │ 2023-03-09T22:38:35+09:00 │
| sessions        | [table 3 rows]            |
╰─────----------──┴──--------─────────────────╯
```

`| get host` で取得できます Elixir でいうところの `|> Map.get(:host)` ですね

:::node info
実際には sessions の右側にテーブルでセッションの一覧が表示されます
:::

更に OS バージョンだけを取得したい場合は以下のようになります

```shell
$ sys | get host.os_version
13.2.1
```

以下のように実行することで、現在システムにログインしているユーザーの一覧が取得できます

```shell
$ sys | get host.sessions.name
╭───┬──────────────╮
│ 0 │ _mbsetupuser │
│ 1 │ oec          │
│ 2 │ root         │
╰───┴──────────────╯
```

そして、ここからが Elixir like というか関数型っぽさというかです

List の各値に対してコマンドを適用することができます

```shell
$ sys | get host.sessions.name | each { |it| ^echo $it }
_mbsetupuser
oec
root
```

アルケミストの皆さんは、この `each` が Elixir でいうところの `Enum.map` であることが分かりますね

:::note warn
map がなくて each な点に注意
:::

`each {|各値を表す変数名| 実行したいコマンド}` で List の各値にコマンドを適用します

上の例で `^echo` のように `^` （ハット）が付いているのは、 Nushell の `echo` ではなく、通常の `echo` を使わせる為です（ `^` がないとテーブル表示になる）

当然、 each があれば reduce もあるわけで、つまりこれを使えば関数型っぽく何でも処理できるわけです

## reduce

例えば `.txt` ファイルの合計サイズは以下のようにして算出できます（もっと別の方法もあると思いますが）

```shell
$ ls | where name =~ ".txt" | reduce -f 0b {|it, acc| $acc + $it.size}
3.7 MB
```

`each {|各値を表す変数名, 前の実行結果を表す変数名| 実行したいコマンド}` で List の各値毎に再帰的にコマンドを適用していき、最終結果を取得します

`-f` で acc （前の実行結果）の初期値を 0　バイト（ファイルサイズ型）に指定しています

## スクリプト

もちろん、スクリプトとして実行することもできます

以下のような内容のファイルを text_sum.nu という名前で保存しましょう

```shell
#!/usr/bin/env nu

ls
| where name =~ ".txt"
| reduce -f 0b {|it, acc| $acc + $it.size}
| format filesize MB
```

実行権限を付けておきます

```shell
chmod +x text_sum.nu
```

これで `.txt` を含むファイルの合計サイズを MB 単位で出力してくれます

以下のようにして実行できます

```shell
$ nu test_sum.nu
3.7 MB
```

また、先頭にシバン（`#!/usr/bin/env nu`）をつけたので、単に `./test_sum.nu` で実行することも可能です

特にパイプで改行するとき `\` によるエスケープが不要なのが良いですね

最早 Elixir と全く同じ感覚で書けます

## まとめ

Nushell は手早くファイルを探索したり操作したりするとき、結構使えそうです

また、Elixir のような書き方は可読性が高く、データ処理の記述に向いていることが再認識できました