nlコマンドで素数を求める


今回はあまり「行番号」にこだわらずnlコマンドを使ってみましょう。


# nlコマンドで整数を生成する

つまりseqコマンドの代替です。`yes`と組み合わせることで無限数列を生成できます。初期値は`-v`、増加値は`-i`、終了値は`head`を組み合わせるとそれぞれ変更可能です。終了値は数列の個数で指定することになるので、`seq`よりも直感的に利用できる場面もあるかもしれません。

```console:coreutils
$ seq 0 2 10
0
2
4
6
8
10
$ yes '' | nl -b a -s '' -w 1 -v 0 -i 2 | head -n 6 
0
2
4
6
8
10
```

これでほぼ等価になります。余計なスペースやタブを入れないように様々なオプションをつけていますが、必須なのは`-b a`のみです。[BSD系の`nl`では`-w`を必要な値の大きさに合わせて設定する](https://qiita.com/Carol_815/items/fa16e4875ba7bc19de86#%E5%B9%85%E3%81%8C%E5%8D%81%E5%88%86%E3%81%A7%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88)必要があります。上記の場合でも`-w`を指定すると`10`が正しく表示されないので、余計なスペースが入ってしまいますが次のようにすべきです。除去が必要なら`sed`などで加工しましょう。


```console:BSD
$ yes '' | nl -b a -s '' -w 1 -v 0 -i 2 | head -n 6
0
2
4
6
8
0
$ yes '' | nl -n ln -b a -s '' -w 10 -v 0 -i 2 | head -n 6
0         
2         
4         
6         
8         
10        
$ yes '' | nl -n ln -b a -s '' -w 10 -v 0 -i 2 | head -n 6 | xxd
00000000: 3020 2020 2020 2020 2020 0a32 2020 2020  0         .2    
00000010: 2020 2020 200a 3420 2020 2020 2020 2020       .4         
00000020: 0a36 2020 2020 2020 2020 200a 3820 2020  .6         .8   
00000030: 2020 2020 2020 0a31 3020 2020 2020 2020        .10       
00000040: 200a                                      .
```

ちなみにGNU coreutilsの`seq`は`inf`というキーワードを指定すれば、無限数列が生成できます。昔は終了値を設定しなければならない`seq`では無限数列が生成できないと思っていたので、「それ`nl`ならできるよ」とかドヤ顔で言ってた時代もありました。

```console:coreutils
$ seq inf | head -n 1000 | tail 
991
992
993
994
995
996
997
998
999
1000
```

ちなみに`inf`についてはManpageにもTexinfoにも書かれていないようです。こういう謎の機能に気づくみなさんは日常的にソースとか読んでるのかな？

# 素因数分解して素数を得る

`nl`の本題からはズレますが、factorコマンドを利用することで簡単に素因数分解した結果を得ることができます。区切られた数列データがれば、素因数分解した結果を得ることができます。

```console
$ seq 5
1
2
3
4
5
$ seq 5 | factor
1:
2: 2
3: 3
4: 2 2
5: 5
$ echo {1..5}
1 2 3 4 5
$ echo {1..5} | factor
1:
2: 2
3: 3
4: 2 2
5: 5
```

この通り`factor`は縦でも横でもいい感じに素因数分解してくれます。ここから素数だけを抽出しましょう。素数とは「`1`と自分自身以外に正の約数を持たない自然数」のことで、一般に`１`は含みません。`2: 2`のように、2つの列からなる行だけを対象に数値を抜き出せば良さそうです。`awk`でフィルタしてみましょう。

```console
$ seq 5 | factor | awk 'NF==2&&$0=$2'
2
3
5
```

素数だけをフィルタすることができました。`awk`のスクリプトの内容ですが、フィールド数（列数`NF`）が`2`なら、行全体（`$0`）に2列目（`$2`）を代入という意味です。上記はアクションレス記法で書いていますが、以下と同じです。

```console
$ seq 5 | factor | awk 'NF==2{print $2}'
2
3
5
```


# 日付のリストを作る

これはdateコマンドを使うのが間違いないと思います。以下のようにフォーマットを指定して、`X`日から`N`日後という日付を出力することができます。

```
$ date -d '20201201 +0days' +'%Y%m%d'
20201201
$ date -d '20201201 +30days' +'%Y%m%d'
20201231
```

つまり2020年12月の日付をすべて得るには、`20201201`を0日目として、30日後までを出力します。この`date`に`xargs`を使って、`nl`で生成した数列を差し込んで実行すれば良さそうです。`-I`で指定した`@`の部分に入力データが挿入されます。

```console
$ yes '' | nl -b a -v 0 | head -n 31 | xargs -I @ date -d '20201201 +@days' +'%Y%m%d'
20201201
20201202
20201203
（略）
20201231
20201231
```

しかし、あらかじめ月をまたがないことがわかっていれば、以下のように`sed`で頭に年月を付加してもいいですね。

```console
$ yes '' | nl -n rz -b a -w 2 | head -n 31 | sed 's/^/202012/'
20201201	
20201202	
（略）
```

あまり深堀っていくと、`nl`を使わずに・・・という話になってくるのでこの辺にしておきます。

```console
$ echo 202012{01..31} 
20201201 20201202 （略）
```

# 素数日のリストを作る

では、つなげて実行してみましょう。

```console
$ yes '' | nl -b a -v 0 | head -n 31 | xargs -I @ date -d '20201201 +@days' +'%Y%m%d' | factor | awk 'NF==2&&$0=$2'
20201227
20201231
```

今年の12月の素数日は12/27と12/31ということがわかりました。

----
# `seq inf`のTexinfo反映

（12/19追記）
`seq inf`についてはTexinfoに使用法を記載してもらうことになりました。ちなみに`sleep inf`もあるらしく、そちらも同時に反映されたようです。

# 余談

本当は[素数日](http://ankokudan.org/d/dl/pdf/pdf-21cprime.pdf)の記事として書こうかなとぼんやり考えていたのですが、残念ながら2020年のAdvent Calendarには素数日がありませんでした。しかし、なんと[次回のシェル芸勉強会](https://usptomo.doorkeeper.jp/events/115134)は12/27開催なので素数日です！