あなたのシェル芸力を伸ばす num-utils のススメ ～ 数値を縦横に合計したい？numsumでできるよ！

## はじめに

シェル芸力を高めるにはさまざまな便利なコマンドを知り、そしてコマンドが持っている機能（オプション）を知り尽くすことが重要です。この記事ではあなたのシェル芸力を伸ばす num-utils について紹介します。

## num-utils とは？

num-utils とはその名の通り、数値を扱うためのコマンドセットです。一つのコマンドではなく複数のコマンドの集まりです。行または行列に対しての数値処理を簡単に行うことができるコマンドセットです。[num-utils の 公式サイトはこちら](https://suso.suso.org/xulu/Num-utils)です。

https://suso.suso.org/xulu/Num-utils

### num-utils を使用する場合の懸念点

num-utils はちょくちょく名前を見かけるコマンドですが一つ懸念点があり、現在メンテナンスが行われていないということです。最終更新日は 2004 年 11 月にリリースされたバージョン 0.5 です。2007 年には 1.0 をリリースするような告知があるのですが、実現されておらず、またメーリングリストなどでの活動も調べた限り見つかりませんでした。すでに実装されている機能は使えますが、一部バグが残っているので注意が必要です。

https://suso.suso.org/xulu/Num-utils_current_list_of_known_bugs

このバグのうち以下のバグは Debian / Ubuntu 系 Linux の公式パッケージ版では修正されていますが、Homebrew 版では修正されていません。

> numprocess
> Power operator (^) does not work - A typo was made on line 151. It should be `$new_number = $new_number**$1`;

```sh
# Debian / Ubuntu 系 Linux では動く
echo 10 | numprocess '/^2/' # => 100

# macOS では動かない
echo 10 | numprocess '/^2/' # =>  0
```

Debian / Ubuntu 系 Linux の公式パッケージ版では他にも修正が加えられているようなので以下のアドレスから確認することをおすすめします。

- [Debian 版の num-utils](https://salsa.debian.org/debian/num-utils)
- [パッチ](https://salsa.debian.org/debian/num-utils/-/tree/debian/master/debian/patches?ref_type=heads)

```txt:Homebrew 版で発生するその他のバグ（警告が出る）
$ range /1..2/
Use of uninitialized value $prefix in concatenation (.) or string at /usr/local/bin/range line 142.
Use of uninitialized value $suffix in concatenation (.) or string at /usr/local/bin/range line 142.
Use of uninitialized value $prefix in concatenation (.) or string at /usr/local/bin/range line 140.
Use of uninitialized value $suffix in concatenation (.) or string at /usr/local/bin/range line 140.
1 2
```

### コマンド名についての注意

num-utils は Debian / Ubuntu 系 Linux で公式にパッケージが提供されていますが、一部のコマンドでコマンド名が変更されています、おそらくその他のパッケージと名前が被ったまたは被りやすいと見なされて変更されたのでしょう。変更されたコマンド名はすべて頭に `num` が追加されています（例 `average` → `numaverage`）。以下の一覧で `(num)` を付けているのが変更されたコマンド名です。

|      コマンド名 | 機能                                         |
| --------------: | -------------------------------------------- |
|   (num) average | 数列の平均値や最頻値や中央値を求める         |
|     (num) bound | 数列から最小値や最大値を見つける             |
|  (num) interval | 数列の中の数値の差を出力する                 |
| (num) normalize | 数列を0から1（指定した範囲）の間で正規化する |
|         numgrep | 数列から任意の条件の数値の検索               |
|      numprocess | 数列に対して任意の計算を行う                 |
|          numsum | 数列や行列を縦や横に合計する                 |
|    (num) random | 数列からランダムな数値を一つ取り出す         |
|     (num) range | 数列を生成する                               |
|     (num) round | 数列の四捨五入・切り上げ・切り下げを行う     |

## コマンド一覧

### numsum - 数列や行列を縦や横に合計する

num-utils でおそらく最も便利なコマンドは `numsum` コマンドではないかと思います。このコマンドは数列や行列を簡単に縦や横に合計する事ができるコマンドです。

```console:数列を合計する（小数対応）
$ seq 1 0.1 2 | numsum
16.5
```

```console:縦方向に列ごとに合計を計算する
$ numsum -c << 'HERE'
1  4  7
2  5  8
3  6  9
HERE
6 15 24
```

```console:横方向に行ごとに合計を計算する
$ numsum -r << 'HERE'
1  4  7
2  5  8
3  6  9
HERE
12
15
18
```

### numrange - 数列を生成する

`numrange` コマンドは指定した表現から数列を生成するコマンドです。`seq` コマンドに似ていますが、条件の異なる複数の数列を同時に生成することができます。数列は `/開始値..終了値[i増分]/` の形で指定し、カンマで複数つなげて指定することができます。

```console:1から3、5、6から10の偶数を出力
$ numrange '/1..3,5,6..10i2/'
1 2 3 5 6 8 10
```

### numrandom - 数列からランダムな数値を一つ取り出す 

`numrandom` コマンドは指定した表現の数列からランダムな値を一つ取り出して出力します。

```console
$ numrandom '/1..100/'
43
```

### numaverage - 数列の平均値や中央値などを求める 

`numaverage` コマンドは数列の平均値や最頻値や中央値を求めるコマンドです。

```console:平均値
$ numrange -N '/1..3,1..5,2,6/' | numaverage
2.9
```

```console:最頻値
$ numrange -N '/1..3,1..5,2,6/' | numaverage -m
2
```

```console:中央値（データの数が偶数個の場合は -l で下の値を取得できる）
$ numrange -N '/1..3,1..5,2,6/' | numaverage -M # 中央に近い二つの値の上の値 
3

$ numrange -N '/1..3,1..5,2,6/' | numaverage -M -l # 中央に近い二つの値の下の値
2
```

### numprocess - 数列に対して任意の計算を行う 

`numprocess` コマンドは数列に対し引数で指定した計算を行うコマンドです。一般的な四則演算に加えて、累乗 `^` や `sqrt`、`sin`、`cons`、円周率 `pi` とネイピア数 `e` の定数が利用可能です。

```console:1から3の数列に5を加えて10倍する
$ seq 3 | numprocess '/+5,*10/'
60
70
80
```

### numinterval - 数列の中の数値の差を出力する 

`numinterval` コマンドは数列の数値の差を出力するコマンドです。

```console
$ numrange -N '/2,4,16,256/' | numinterval
2
12
240
```

### numbound - 数列から最小値や最大値を見つける

`numbound` コマンドは数列の中の最大値と最小値を見つけ出すコマンドです。

```console:最大値を出力
$ numrange -N '/5,8,3,6/' | numbound
8
```
```console:最小値を出力
$ numrange -N '/5,8,3,6/' | numbound -l
3
```

### numgrep - 数列から任意の条件の数値の検索

`numgrep` コマンドは数列の中から条件にマッチした数値を出力するコマンドです。

```console:3から5の範囲を出力
$ seq 10 | numgrep '/3..5/'
3
4
5
```

```console:3の倍数を出力
$ seq 10 | numgrep '/m3/'
3
6
9
```

```console:12を割り切れる数を出力
$ seq 10 | numgrep '/f6/'
1
2
3
6
```

### numround - 数列の四捨五入・切り上げ・切り下げを行う 

`numround` コマンドは数列を四捨五入、切り上げ、切り下げを行うコマンドです。

```console:四捨五入を行う
$ numrange -N '/10.4,10.5,10.6,-10.5/' | numround
10
11
11
-11
```

```console:切り上げを行う
$ numrange -N '/10.4,10.5,10.6,-10.5/' | numround -c
11
11
11
-10
```

```console:切り下げを行う
$ numrange -N '/10.4,10.5,10.6,-10.5/' | numround -f
10
10
10
-11
```

### numnormalize - 数列の合計値を1として値を正規化する 

`numnormalize` コマンドは数列の合計を 1 (100%) として、それぞれの値を 0 から 1 の間で正規化するコマンドです。

```console:1 + 2 + 3 = 6 を 1 として考えて正規化を行う
$ seq 3 | numnormalize
0.166666666666667     👈 1/6
0.333333333333333     👈 2/6
0.5                   👈 3/6
```

## さいごに

num-utils を使うとちょっとした数値の計算が簡単に行うことができます。特に行や行列の合計が簡単にできるのは便利で、awk を使うまでもないというようなときに使うことができます。更新が行われておらずバグが残っているのが少々気がかりなので、誰か後を引き継いで修正したり Rust 実装版などを作ってくれないかなー？なんて思っています。