CentOS 7 の mysql コマンドでの日本語入力

某所で CentOS 7 で mysql コマンドで日本語入力ができないという話を見かけた。 mysql> プロンプトで日本語を入力しようとしても確定すると消えてしまうらしい。 自分は Ubuntu で普通に入力できてるので調べてみた。

結論からいうと、これはロケールの問題で locale -a コマンドの出力中に ja_JP.utf8 があって、ロケール(環境変数 LC_ALL, LC_CTYPE, LANG 等の値)が ja_JP.UTF-8 になっていれば問題ない。

もう一つ。CentOS 7 で絵文字を入力すると、絵文字ではなく \U+1F37A みたいに表示される。表示だけの問題なので動作上は問題ない。

これは OS の glibc のバージョンの問題ぽいので CentOS 7 ではどうしようもなさそう。

以下調査内容。

MySQL は mysql-8.0.22-linux-glibc2.12-x86_64.tar.xz を使用。

% docker run --name centos7 -h centos7 -it centos:7 bash
[root@centos7 /]# yum install -y libaio numactl
[root@centos7 /]# useradd -rm mysql
[root@centos7 /]# cd /usr/local
[root@centos7 local]# tar xf /tmp/mysql-8.0.22-linux-glibc2.12-x86_64.tar.xz
[root@centos7 local]# ln -s mysql-8.0.22-linux-glibc2.12-x86_64 mysql
[root@centos7 local]# ./mysql/bin/mysqld --user=mysql --initialize-insecure
[root@centos7 local]# ./mysql/bin/mysqld --user=mysql --daemonize
[root@centos7 local]# ./mysql/bin/mysql
mysql>
↑ここで日本語を入力しても確定すると消えてしまう

locale -a の結果に ja が無い。

[root@centos7 local]# locale -a | grep ja

ロケールを追加。

[root@centos7 local]# localedef -f UTF-8 -i ja_JP ja_JP.UTF-8
[root@centos7 local]# locale -a | grep ja
ja_JP.utf8
[root@centos7 local]# export LC_ALL=ja_JP.UTF-8
[root@centos7 local]# ./mysql/bin/mysql
mysql> あいうえお
↑入力できた

よく考えたら UTF-8 でさえあれば別に ja である必要はなかったので、ロケールを追加しなくても最初から存在してる en_US.UTF-8 でも良かった。

[root@centos7 local]# export LC_ALL=en_US.UTF-8
[root@centos7 local]# ./mysql/bin/mysql
mysql> あいうえお
↑入力できた

まあ日本語を使うなら ja_JP.UTF-8 にしといた方がいいような気はする。縁起物なので。

日本語が入力できるようになったけど、🍣🍺 などの絵文字を入力すると \U+1F363\U+1F37A になってしまう。MySQL の動作的には問題ない。

mysql> select '\U+1F363';
+------+
| ?    |
+------+
| 🍣     |
+------+

\U+1F363 の後にカーソルがある状態で左に移動すると \U+1F363 の先頭の \ に移動するので、行編集上は \U+1F363 が1文字として扱われていることがわかる。

行編集ライブラリの readline の問題かと思ったけど、どうやら readline ではなく互換ライブラリの libedit を使っているらしい。

静的リンクされているので、ldd ではわからなかったが、strings で判明。 readline は $HOME/.inputrc を使うが libedit は $HOME/.editrc を使うため。

[root@centos7 local]# ldd mysql/bin/mysql
    linux-vdso.so.1 =>  (0x00007ffd64d2e000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fca79068000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fca78e60000)
    libcrypto.so.1.1 => /usr/local/mysql/bin/../lib/private/libcrypto.so.1.1 (0x00007fca789af000)
    libssl.so.1.1 => /usr/local/mysql/bin/../lib/private/libssl.so.1.1 (0x00007fca7871f000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fca7851b000)
    libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fca78301000)
    libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fca780d7000)
    libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007fca77dd0000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fca77ace000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fca778b8000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fca774ea000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fca79284000)

[root@centos7 local]# strings ./mysql/bin/mysql | grep inputrc
[root@centos7 local]# strings ./mysql/bin/mysql | grep editrc
/.editrc

libedit のソースをテキトーに grep したらそれっぽい処理を発見: https://salsa.debian.org/debian/libedit/-/blob/8fb436cf/src/chartype.c#L270

iswprint() が偽になったらこのような動きになるっぽい: https://salsa.debian.org/debian/libedit/-/blob/8fb436cf/src/chartype.c#L335

C でテキトーなサンプルを作って確認してみる。

#include <stdio.h>
#include <locale.h>
#include <wchar.h>
#include <wctype.h>

int main(int argc, char *argv[])
{
  setlocale(LC_CTYPE, "");
  wint_t wc;
  while ((wc = getwc(stdin)) != WEOF) {
    printf("%d\n", iswprint(wc));
  }
  return 0;
}
[root@centos7 tmp]# gcc test.c
[root@centos7 tmp]# echo -n aあ🍣 | LC_ALL=ja_JP.UTF-8 ./a.out
16384  ← 「a」は真
1      ← 「あ」は真
0      ← 「🍣」は偽

ちなみに Ubuntu 20.10 で試すとこうなる。

% echo -n aあ🍣 | LC_ALL=ja_JP.UTF-8 ./a.out
16384  ← 「a」は真
1      ← 「あ」は真
1      ← 「🍣」は真

iswprint() は glibc の関数で、CentOS 7 の glibc は 2.17、Ubuntu 20.10 は 2.32 なのでおそらく glibc が古いせいだと思う。

というわけでこれについては CentOS 7 では諦めるのがよさそう。なお CentOS 8 では問題なかった。