sort -u と sort | uniq は同じじゃないよ ～ 文字の順番とロケールとPOSIXと現実の実装のめんどくさい話

## はじめに

`sort` コマンドの `-u` オプションは重複行を取り除くオプションです。`-u` は [1979 年の Vesion 7 Unix](https://man.cat-v.org/unix_7th/1/sort) のときからある古いオプションで、シェルスクリプトに詳しいと自負している人でこのオプションを知らなければモグリでしょう。`uniq` コマンドは重複行を取り除くコマンドです。**「現在のすべての問題が解決された場合」は `sort -u` と `sort | uniq` はほぼ同じになる**のですが、実際には異なる結果を返すことがあります。この記事ではその問題について解説します。

## 前提知識: sort-u と uniq の正しい理解

この記事の話とは少し異なりますが前提知識として `sort` コマンド `-u` オプションと `uniq` コマンドの正しい機能について簡単に解説します。

### 文字の順番はロケール依存で環境ごとに違う

文字の順番はロケールによって異なります。参考として ASCII コードを載せておきます。ここで重要なのは**アルファベットの大文字は小文字よりも小さい**ということです。ASCII コードを見ればその理由は明らかですが、小文字のほうが大きいというのは不自然ですよね？

```txt
| HEX     |  +0  +1  +2  +3  +4  +5  +6  +7 |  +8  +9  +A  +B  +C  +D  +E  +F |
|     OCT |                                 | +10 +11 +12 +13 +14 +15 +16 +17 |
|=========|=================================|=================================|
| 00h:000 | NUL SOH STX ETX EOT ENQ QCK BEL | BS  HT  LF  VT  FF  CR  SO  SI  |
| 10h:020 | DLE DC1 DC2 DC3 DC4 NAK SYN ETB | CAN EM  SUB ESC FS  GS  RS  US  |
| 20h:040 | SP   !   "   #   $   %   &   '  |  (   )   *   +   ,   -   .   /  |
| 30h:060 |  0   1   2   3   4   5   6   7  |  8   9   :   ;   <   =   >   ?  |
| 40h:100 |  @   A   B   C   D   E   F   G  |  H   I   J   K   L   M   N   O  |
| 50h:120 |  P   Q   R   S   T   U   V   W  |  X   Y   Z   [   \   ]   ^   _  |
| 60h:140 |  `   a   b   c   d   e   f   g  |  h   i   j   k   l   m   n   o  |
| 70h:160 |  p   q   r   s   t   u   v   w  |  x   y   z   {   |   }   ~  DEL |
```

これを Ubuntu 22.04 の環境で日本語ロケールと英語ロケールで実行すると以下のようになります。

```console
$ cat file.txt
apple
Banana
Apple
banana

$ LC_ALL=ja_JP.UTF-8 sort file.txt # 日本語ロケール
Apple
Banana
apple
banana

$ LC_ALL=en_US.UTF-8 sort file.txt # 英語ロケール
apple
Apple
banana
Banana
```

日本語ロケールと英語ロケールでの順番の違いは照合順序の違いによるものです。照合順序とはロケール情報に含まれている文字の順番のことです。アルファベットの大文字と小文字は日本語ロケールでは ASCII コードと同じように「ABC... abc...」の順並んでいますが、英語ロケールでは「aAbBcC...」の順で並んでいます。意味としてはこの方が自然です。この順番は OS が定義しているものなので環境によって異なります。

| 環境            | 日本語ロケール | 英語ロケール |
| --------------- | -------------- | ------------ |
| Ubuntu 22.04    | ABC... abc...  | aAbBcC...    |
| Solaris 11.4.42 | ABC... abc...  | aAbBcC...    |
| FreeBSD 13.2    | aAbBcC...      | aAbBcC...    |
| NetBSD 9.3      | ABC... abc...  | ABC... abc... |
| OpenBSD         | ABC... abc...  | ABC... abc... |
| macOS 13.4      | ABC... abc...  | ABC... abc... |

補足: NetBSD では `LC_ALL` を設定しても `LC_COLLATE="C"` から変更できません。

```sh:確認方法
printf '%s\n' A a b B  | LC_ALL=ja_JP.UTF-8 sh -c 'locale; sort'
printf '%s\n' A a b B  | LC_ALL=en_US.UTF-8 sh -c 'locale; sort'
```

### `-u` はソート順が同じ行の重複を省く

`sort -u` は確かに `uniq` 相当の働きをするのですが、その他のオプションを指定した場合は意味が違います。例えば `-f` オプションは大文字と小文字の違いを無視するオプションです。より正確に言うと小文字を大文字とみなすオプションです。このオプションを `-u` オプションと共に指定するとどうなるでしょうか？ なお環境は Ubuntu 22.04 ですが、他の環境でも同じです。

```console
$ cat file.txt # 先程の例と内容は同じ
apple
Banana
Apple
banana

$ LC_ALL=ja_JP.UTF-8 sort -f -u file.txt # 日本語ロケール
apple
Banana

$ LC_ALL=en_US.UTF-8 sort -f -u file.txt # 英語ロケール
apple
Banana
```

行が減ってしまいましたよね？ これは「apple」と「Apple」が同じソート順だからです。まず「apple」と「Apple」は `-f` オプションの効果でどちらも「APPLE」として比較されます。どちらも「APPLE」なのでソート順は同じです、よって重複行とみなして削除されたのです。この例は `-f` オプションを使用した例なので本記事の内容とは別の話ですが、`sort -f -u` と `sort -f | uniq` の動作も違うということがわかります。

```console:参考: -u を指定しない場合
$ LC_ALL=ja_JP.UTF-8 sort -f file.txt
Apple
apple
Banana
banana

$ LC_ALL=ja_JP.UTF-8 sort -f file.txt | LC_ALL=ja_JP.UTF-8 uniq
Apple
apple
Banana
banana
```

もう一つの例です。`sort` コマンドは `-k` オプションで比較するフィールド（ソートキー）を指定することが出来ます。

```console
$ cat file2.txt # 頭に番号がついている
123 apple
123 Banana
123 Apple
123 banana

$ sort -k 1,1 -u file2.txt
123 apple
```

`sort -k 1,1 -u file2.txt` で一行しか出力されない理由は、ソートに利用しているフィールドが1フィールドのみなので、1フィールドは全部123なのでソート順は同じ = 重複行とみなしているからです。このようにソート順が同じ行を省くのが `sort -u` の機能です。

補足ですが 1フィールド目のみを指定する時に `-k 1` と書かないように注意してください。`-k 1` は `-k 1,1` と同じ意味ではありません。**`-k 1` の正確な意味は 1フィールド目から最後のフィールドまで**です（上記の例では `-k 1,2` と同じ）。

### ラストリゾート（✕ 最後の楽園、○ 最終手段）の比較

GNU sort では「最終手段の比較 (last-resort comparison)」と呼ばれる比較が行われています。これはソートキーの条件に従って比較した結果、同一の行と判断された行に最後に行う「行全体を使った比較」のことです。**FreeBSD（OpenBSD と macOS を含む）の `sort` は、現在は FreeBSD 独自の実装が使われていますが、過去に GNU sort を使っていた**関係から、GNU sort の互換コマンドになっている（例えば `--version` などのロングオプションも実装されている）ため同様の比較が行われていると思われます。[AIX sort](https://www.ibm.com/docs/ja/aix/7.3?topic=s-sort-command) でもおそらく同様の比較が行われています。

> 2 つの行がすべてのソート・キーに対して等しくソートされる場合、行全体が現行ロケールの照合順序に基づいて比較されます。

補足すると `-s` （安定ソートを行う）オプションは GNU sort などの「最終手段の比較」を行っている実装では「最終手段の比較」によるソートを無効にするオプションです。ちなみに **NetBSD 版の sort コマンドはデフォルトで安定ソートを行う**ので注意してください。つまり `-s` オプションを指定しない場合、その順番は「最終手段の比較による順番」か「決まっていない順番」か「安定ソートを行った順番」のどれかです。また `-s` オプションは Solaris や AIX では実装されていません。

「最終手段の比較」は GNU sort の `--debug` オプションで視覚的に見ることができます。

```console:最終手段の比較
$ sort --debug -k 1,1 -k 3,3 << 'HERE' 2>/dev/null
foo    bar    baz
HERE
foo    bar    baz
___                       【1. -k1,1の比較範囲】
          _______         【2. -k3,3の比較範囲】
_________________         【3. 「最終手段の比較」の比較範囲】
```

ソートで指定しているキーは、1フィールド目と3フィールド目のみですが、最終的に行全体を使って比較をしているということが示されています。ときどき `-k 1,1` のみを指定して同一行と見なされるときでもソートされているのを見て、2フィールド目、3フィールド目の比較も行われているんだなと間違って解釈している人がいますが、**同一行と見なされた時に行われる比較は残りのフィールドではなく行全体**なので注意してください。

```console:-sオプションを指定した場合（「最終手段の比較」なし）
$ sort --debug -k 1,1 -k 3,3 -s << 'HERE' 2>/dev/null
foo    bar    baz
HERE
foo    bar    baz
___                       【1. -k1,1の比較範囲】
          _______         【2. -k3,3の比較範囲】
```

`-s` オプションを指定すると「最終手段の比較」が行われていないことが示されています。

### `uniq` コマンドはユニーク行に関する情報のレポート

英語に uniq (unique) という動詞はありません。unique は形容詞です。動詞ではないので `uniq` コマンドは「一つにする」という機能を意味する名前ではありません。`uniq` コマンドはユニーク行に関する情報をレポートするコマンドです。

ユニーク行の情報と聞いてどんな機能を思いつくでしょうか？例えばユニーク行はどの行か？ユニーク行ではない行はどの行か？という情報を出力したいですよね？それが `-u` オプションと `d` オプションです。ユニーク行とユニーク行以外の行数も数えられると便利です。それが `-c` オプションです。ちなみに `-c` オプションは環境によっては `-u` や `-d` と組み合わせて使うことは出来ません。


```console
$ cat file3.txt
foo
foo
bar
foo

$ uniq -c file3.txt # それぞれの数を数える
      2 foo
      1 bar
      1 foo
      
$ uniq -u file3.txt # ユニーク行（-cの出力で1個の行）の出力
bar
foo

$ uniq -d file3.txt # 上記の反対（-cの出力で1個以外の行）の出力
foo
```

先程から「ユニーク」と言っていますが、実際には同じ行が連続している回数のことです。知っての通り事前にソートして同じ行を隣接させないとファイル全体からユニークであるかどうかはわかりません。

`uniq` コマンドはユニーク行に関する情報のレポートするコマンドであることを理解すると、`uniq` コマンドが持っているさまざまな情報を出力するオプションの存在が当たり前のように感じられるはずです。Unix 哲学に「一つのことをうまくやる (do one thing and do it well)」という考え方がありますが、 `-c`、`-u`、`-d` オプションは「ユニーク行を扱う」という「一つのこと (one thing)」に関する「機能 (feature)」を選択するオプションであって、「重複行を一つにする」という機能に対するオプションではありません。**「一つのこと」とは一つの機能のことではなく、Unix 哲学は一つのコマンドに一つの機能しか持たせないという考え方ではない**ことを示す例です。Unix 哲学で重要なのはコマンドを組み合わせられるように作ることであって、単機能のコマンドを作ることではありません。

## sort -u と sort | uniq が同じではない理由

さて、ここまでは前提知識です。ここからが本題で**前提知識の内容は直接関係しない**ので頭の片隅において頭の中を一旦リセットしてください。

`sort` コマンドの `-u` オプションと `uniq` コマンドの違いは照合順序（ロケールの持つ文字の順番）に基づいた処理をするかどうかです。`sort` は照合順序に基づいた処理を行いますが、`uniq` コマンドは照合順序に基づいた処理を行いません。

### POSIX はなんと言っているか？

[POSIX の `sort` コマンドの説明](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html)を参照します。

> Comparisons shall be based on one or more sort keys extracted from each line of input (or, if no sort keys are specified, the entire line up to, but not including, the terminating <newline>), and shall be performed using the collating sequence of the current locale. If this collating sequence does not have a total ordering of all characters (see XBD LC_COLLATE), any lines of input that collate equally should be further compared byte-by-byte using the collating sequence for the POSIX locale.

重要なポイントをざっくり説明すると以下のようになります。

1. 現在のロケールの照合順序でソートする
2. 照合順序にすべての文字の順番が定義されていない場合に、異なる行で等しいと判断されてしまう行は POSIX ロケールの照合順序でバイトごとに比較すべきである (should)

上記は現在の POSIX の Issue 7 での話です。この内容が加わったのは、POSIX Issue 7 の 2018年の修正版からです。この件に関しては 2015年から2016年にかけて議論が行われています（[0000963: Collation issues in XCU (changes for TC2)](https://www.austingroupbugs.net/view.php?id=963)）。話の流れとしては POSIX は以前はこのように決まっていたけど問題があるので Issue 8 での解決を目指して修正していきましょうという流れです。

- [POSIX Issue 7（2013年版）](https://pubs.opengroup.org/onlinepubs/9699919799.2013edition/) まだない
- [POSIX Issue 7（2018年版）](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/sort.html) 追加されている

次の改定の Issue 8 では 2. が「比較されなければならない (shall)」に変更になります。このことを覚えておいてください。2.の「照合順序にすべての文字の順番が定義されていない場合」というのは定義上は例外的な状態で、普通はすべての文字に順番が定義されているべきものです。定義上はね。[POSIX の照合順序に関する記述](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap07.html#tag_07_03_02)から重要なポイントを引用します。

> All implementation-provided locales (either preinstalled or provided as locale definitions which can be installed later) should define a collation sequence that has a total ordering of all characters unless the locale name has an '@' modifier indicating that it has a special collation sequence (for example, @icase could indicate that each upper and lowercase character pair collates equally).
> 
> Notes:
> 
> A future version of this standard may require these locales to define a collation sequence that has a total ordering of all characters (by changing "should" to "shall").
> 
> Users installing their own locales should ensure that they define a collation sequence with a total ordering of all characters unless an '@' modifier in the locale name (such as @icase ) indicates that it has a special collation sequence.

重要なポイントをざっくり説明すると以下のようになります。

1. ロケール名に `@` が含まれているものは特別な照合順序を持っている
2. 上記以外はすべての文字に完全な順番を定義するべきである (should)
3. 将来の POSIX の場合は「定義すべきである (should)」から「定義しなければならない (shall)」に変更するかもしれない。

この 2. は 3.で述べられているように Issue 8 で「定義しなければならない (shall)」に変更になります。しかし、POSIX Issue 7 までの仕様では強制ではありませんでした。このことを覚えておいてください。

さて一方で `uniq` コマンドですが、こちらは照合順序の話は関係なくバイト単位での比較です。話は簡単ですね。

POSIX の仕様では `sort` コマンドの `-u` と `uniq` コマンドが違うらしいというのはわかった。それで実際どのような問題があるのか？というのが多くの人の気になる話でしょう。**POSIX には実際の動作は書いていないので POSIX だけを読んでいても何の役にもたちません**。

### 実際に発生する「sort -u」と「sort | uniq」の違い

前述の「このことを覚えておいてください」、二つの should のおさらいです。

1. 照合順序にすべての文字の順番が定義されていない場合に、異なる行で等しいと判断されてしまう行は POSIX ロケールの照合順序でバイトごとに比較すべきである (should)
2. （ロケール名に `@` が含まれているもの以外は）すべての文字に完全な順番を定義するべきである (should)

ここで特に重要なのは 1. です。2. に関してはすべての文字に完全な順番を定義されれば影響範囲が大幅に小さくなるとはいえ、どちらにしろロケールに `@` が含まれているものには完全な順番が定義されない場合があります。

「sort -u」と「sort | uniq」の違いは、**異なる行で等しいと判断されてしまう行に対して POSIX ロケールの照合順序でバイトごとに比較するという処理が実装されていない**事により発生します。そしてそれが実装されていないのが（少なくとも）GNU sort です。その他の実装も怪しい感じがしますが詳しくは調べていません。どちらにしろ一番よく使われるであろう GNU sort で問題があるのだから他の実装で問題ないからと言って無視する訳にはいかないでしょう。

「異なる行で等しいと判断されてしまう行に対して POSIX ロケールの照合順序でバイトごとに比較する」というのは要するにGNU sort が行っている「最終手段の比較」のことです。ですが現在の実装は **POSIX ロケールではなく現在のロケールの照合順序で比較**されています。「最終手段の比較」は元々ソートキーを指定した場合のソート順を定義するためのものでロケールまで考慮されていなかったのでしょう。雑な検証ですが [sort.c](
https://github.com/coreutils/coreutils/blob/7f2c97a2411e66c32ef8e6818416a0a2231c5b8e/src/sort.c#L2837) にこのような修正を入れたら期待したとおりになりました。

```c
   else if (hard_LC_COLLATE)
     {
       /* xmemcoll0 is a performance enhancement as
          it will not unconditionally write '\0' after the
          passed in buffers, which was seen to give around
          a 3% increase in performance for short lines.  */
       diff = xmemcoll0 (a->text, alen + 1, b->text, blen + 1);
+      if (diff == 0) {
+        setlocale(LC_ALL, "C");
+        diff = xmemcoll0 (a->text, alen + 1, b->text, blen + 1);
+        setlocale(LC_ALL, "");
+      }
     }
```

ただし、もしここで現在のロケールですべての文字の完全な順番が定義されていれば大きな問題は発生しませんでした。という言い方から分かるように実際にはすべての文字に対して完全な順番が定義されていないということです。大雑把に言えば Unicode で新しく追加された文字などには順番が定義されていないようです（すべて順番が0相当のように思える）。[Stéphane Chazelas の 2019 年の回答](https://unix.stackexchange.com/questions/76049/what-is-the-difference-between-sort-u-and-sort-uniq)によると、GNU libc 2.30 の時点で 95% 以上のコードポイントが定義されていないようです。現在の最新の GNU libc は 2.38 です。

>  2019 edit. Those have since been fixed, but over 95% of Unicode code points still have an undefined order as of version 2.30 of the GNU libc. You can test with 🧙🧚🧛🧜🧝 instead for instance in newer versions


さて、それでは具体的な違いを見てみましょう。環境は Ubuntu 22.04、23.04、23.10、Almalinux 9.2 です。`export POSIXLY_CORRECT=1` を設定して厳密に POSIX に準拠させても結果は同じです。

```sh:Ubuntu 22.04、23.04、23.10、Almalinux 9.2 での結果
$ LC_ALL=ja_JP.UTF-8 sort -u <<'HERE'
①
②
㍻
㋿
HERE
①          【sort コマンドでは上記の文字はすべて同じと判断されている】

$ LC_ALL=ja_JP.UTF-8 uniq <<'HERE'
①
②
㍻
㋿
HERE
①
②
㍻
㋿
```

本来は「①」「②」「㍻」「㋿」が同一の文字と判断されるべきではありません。文字が正しく定義されていれば、このような結果にはならないはずですが、同一の文字と判断されるということは照合順序が定義されていないことを意味しています。ロケールに関する情報は `/usr/share/i18n/locales/ja_JP` ファイルの `LC_COLLATE` セクションに定義されています。詳しくは見ていませんが `2460` などのコードポイントが記載されていないことは確認しました。

### uniq コマンドなら問題ない･･･わけないじゃない！

**「ふーん、じゃあ `-u` オプションダメじゃん。`uniq` コマンドを使わなきゃ。」と考えるのは早計**です。なぜなら `uniq` コマンドも Ubuntu 16.04、18.04、20.04、Almalinux 8.8 という古い環境では正しく動かないからです。結果から推測するに `uniq` コマンドも（POSIX の規定とは反して）照合順序を使っているようです。こちらも `export POSIXLY_CORRECT=1` を設定して厳密に POSIX に準拠させても結果は同じでした。

```sh:Ubuntu 16.04、18.04、20.04、Almalinux 8.8 での結果
$ LC_ALL=ja_JP.UTF-8 sort -u <<'HERE'
①
②
㍻
㋿
HERE
①

$ LC_ALL=ja_JP.UTF-8 uniq <<'HERE'
①
②
㍻
㋿
HERE
①
```

ということは結果は正しくありませんが古い環境では『「sort -u」と「sort | uniq」は同じ』と言えます。良かったね（？）。

Ubuntu 16.04、18.04、20.04、Almalinux 8.8 が古いと言っても、[Ubuntu 18.04 は有償の Ubuntu Pro が2028年4月までのサポート](https://jp.ubuntu.com/blog/18-04-end-of-standard-support-jp)、20.04は 2025年4月までの無償サポート、AlmaLinux 8 系は 2029年までのサポートなのでまだ利用者は多いでしょう。

ちなみに [POSIX Issue 7 の uniq](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/uniq.html)の APPLICATION USAGE では `sort | uniq` の代わりに `LC_ALL=C sort -u | sort` を使えと書かれています。`sort` が二つあるのがおしゃれ（？）ですね。POSIX Issue 8 ではこの記載は削除され「現在のロケールですべての文字に完全な照合順序が定義されていない場合は `sort | uniq` は `sort -u` と動作が異なる」という内容に変更されました。POSIX Issue 8 では一般的なロケールの定義はすべての文字に完全な照合順序が定義されなければならないと変わったからです。だからといって現実の実装がそれに追いついているわけではないので、POSIX がそう言っているからと言っても、それは机上の空論なわけです。**どの環境でも同じように動くシェルスクリプトを書くには POSIX だけを見ていても意味がない**という実例です。

## 安全にソートして重複を省くするにはどう書くべきか？

利用者の立場からの対策はロケールを C ロケールに変更してソートすることです。

```sh
# 安全な書き方
LC_ALL=C sort -u

# 古い環境を考慮した sort | uniq の使い方
LC_ALL=C sort | LC_ALL=C uniq
```

POSIX に準拠した形で正しく実装されてそれが広まればこんなことをしなくても普通に書けるんですけどね。何年先になることやら。

`LC_ALL` を変更するのが楽ですが、少々乱暴すぎると考える場合、少なくとも `LC_COLLATE` と `LC_CTYPE` を変更する必要があります。またこの問題は `sort` コマンドだけの話ではありません。`join` コマンドや `comm` コマンドなどもデータがソートされていることを前提としているので関係ありますし、`expr` コマンドなどの文字列の比較も照合順序によって行われるので関連があります。

もういっそのことすべて C ロケールにしちゃえという考えもあるかもしれませんが、そうすると今度は日付が英語で出力されたり、UTF-8 文字を1文字として扱えないので `sed` コマンドでの 1文字の置換などで問題が発生します。**すべてで C ロケールを使うことを選んでしまったら今度は自力で UTF-8 文字の解釈を行うことになってしまうでしょう。UTF-8 文字の扱いを awk コマンドなどで実装しようものなら大幅に遅くなるのは言うまでもなりません**（前に作ったはずだけどどこ行ったっけ？）。C.UTF-8 ロケールの方がおそらくマシですが、macOS 13.4 や Solaris 11.4 にはありません（Solaris 11.4.42にはある）。

文字列をデータとして扱う場合は C ロケールのほうが良くても、端末に出力する場合はユーザーが設定したロケールのほうが良い可能性があります。つまり出力先で使用するロケールを切り替えるということです。面倒ですがユーザーフレンドリーというのはこういうことです。結局のところ、コマンド毎にロケールの問題を正しく知って適切に使い分ける必要があります。

## まとめ

- **`sort -u` と `sort | uniq` は現在の問題がすべて解決されれば一部の例外を除き同じ**
- 現在の Linux システムは、すべての文字に完全な照合順序が定義されていない
- 多くの sort コマンドは POSIX ロケールではなく現在のロケールで「最終手段の比較」を行っているため、異なる文字列が同一とみなされる場合がある
- 少し前のバージョンの `uniq` コマンドも現在のロケールで重複行を省いているため、異なる文字列が同一とみなされて削除される場合がある
- 完全に POSIX に準拠したシステムであっても `sort -u` と `sort | uniq` は例外的な「すべての文字に完全な照合順序が定義されていない場合」において違いがある
- **現時点では安全に重複行を除くのであれば `LC_ALL=C sort -u` または `LC_ALL=C sort | LC_ALL=C uniq` と書く必要がある**

Linux 以外ではおおむね問題ないように思えますが、おそらくすべて（もしくはほとんど）の文字に完全な照合順序が定義されているからだと思われます。ここで Linux は POSIX に準拠してないと責めることはできません。なぜなら現在の POSIX Issue 7 ではまだ推奨されているだけで必須にはなっていない仕様だからです。POSIX では 2016 年頃に気づいた問題で 2018 年から推奨になった仕様です。また互換性の問題やらパフォーマンスの問題もあるので変更して問題ないかと言われたら悩む話です。

## さいごに - シェルスクリプトの日本語処理はめんどくさい

シェルスクリプトがこのようなめんどくさい話になっているのは、歴史的な Unix コマンドが ASCII 文字しか考慮しておらず、POSIX が標準化された 1988 年（シェルとコマンドは 1992 年）にはまだ UTF-8 が登場していなかったからです。わずかに UTF-8 の誕生のほうが早いかもしれませんが標準化の作業に間に合うわけはありません。Unix コマンドは OS が一括して開発しているように思えるかもしれませんが、実はバラバラの人が自分の考えで作ったものの寄せ集めに過ぎません。したがって統一的な思想や設計もありません。そして歴史的な Unix コマンドがそのころから大きな変化がないのは、1990 年代に Unix を開発した AT&T が Unix 開発から撤退し、Unix の開発がほぼ停止してしまったからです。メンテナンス的な開発は続いていますが、歴史的な Unix コマンドは大きく変わらず不便なままです。**新しいことをしたい場合や、より便利なコマンドは OS 以外のコマンドを使えば良い**ので、Unix の開発ベンダーは歴史的な Unix コマンドには後方互換性しか求めておらず、OS の機能として含める必要がない、OS の機能は今までのものが動けば十分というのが今の時代です。POSIX もまた新しい機能を追加するようなものではないので、**Unix コマンドが変わらないというのは良いことではなく 1990 年代からずーっと不便なまま**なのです。POSIX は移植性のための標準規格なので、特定の OS ベンダーが必要だと思って実装した機能でも、それが直ちに POSIX に組み込まれることは有りません。**POSIX で標準化された機能の範囲には現代の基準でほぼ必須の重要な機能ですらない**ということなのです。そもそも日本語処理って OS の機能として必要ですか？ 日本語処理が重要なら他のプログラミング言語を使いましょうよ。

ロケールへの対応が不完全な歴史的な Unix コマンドは、後方互換性を維持するために変更することは簡単ではないので、この状況がすぐに良くなることは考えられません。たとえどれかの実装が対応しても別の実装では対応されていないと言うともあります。**Unix コマンドは、日本語などの ASCII 文字以外のテキスト文字をまともに扱うの難しい**ということです。そこでシェルスクリプトの高度なデータ管理に必要なのが SQLite などのコマンドラインから使えるデータベースです。SQLite は設定やサーバーが不要な RDBMS なので、SQL という普遍の知識を活かせますし C 言語で作られ POSIX に完全に準拠しておりオープンソースでどの環境でも使うことが出来ます。Unix コマンドは個々のコマンドがバラバラに開発されているためうまくつなぎ合わせるために苦労しなければいけませんが、SQLite はすべての機能を統合して扱っているのでソート順がどうとかテーブルを結合する時の注意点とかがありません。UTF-8 にも OS のローケル機能を使わずに対応しています。Unix コマンドのような環境ごとの差がないので、環境の違いや POSIX に準拠するためのあれこれを考えなくても、簡単なインストール作業を行うだけで一度書けばどの環境でも動きます。SQLite は C 言語で作られていますが **POSIX に準拠するコマンドはどの言語でも作れます**。最終的に POSIX の C 言語インターフェースを呼び出していれば、POSIX 準拠です。**あの POSIX が「C 言語でなければ POSIX 準拠にりません！」などと 他の言語を排除したりソフトウェア開発が変化していくことを否定するようなことを言うわけがありませんよね？**。もちろんシェルスクリプトだけではなく他の言語も使ってください。**シェルスクリプトはさまざまな言語で作られたプログラムを組み合わせるためのものですから、多くの言語やコマンドを使って組み合わせることが、シェルスクリプトの正しい使い方**です。

やっぱりですね、**1990 年代前半までの OS に依存した環境依存の問題が大きい古い Unix コマンドと、それ以降に登場した OS に依存しない移植性が高い新しいコマンドでは大きな差がある**ということです。だって古い Unix コマンドは変化（＝改良）してないのですから。Unix コマンドは未完成のまま舵を取る組織がなくなり、その状態で移植性があった僅かなコマンドだけが POSIX で標準化され、各 OS がバラバラに必要最小限の機能を独自の拡張機能として追加しているものです。何十年も変わらないので、新しいことを覚えなくてすむという（勉強したくない人にとっては）メリットがあるかもしれませんが、結局のところそれはいつまで 1990 年代の不便なままだということです。残念ながら歴史的な Unix コマンドにシェルスクリプトの未来は作れません。それらは新しいことをするものではなく後方互換性の方が重要なので仕方のない話です。歴史的な Unix コマンドで日本語を含むデータ処理を行うという「新しいこと」をするのはやめて、ASCII 文字（ただし 8 ビットの範囲まで許可）だけで扱えるようなことに使うべきでしょう。そして日本語を含むテキストデータの処理を行うのであれば、ちゃんと UTF-8 に対応した新しいコマンドを使うべきだということです。新しいコマンドはすでにたくさんあるのでそれを便利に使えば、日本語を含むデータ処理も簡単に行うことができるでしょう。