Posted at

Unicode結合文字列の怪 Mac OS X & fish shell & JavaScript編

More than 3 years have passed since last update.


最初に結論


  1. Mac OS Xではシェルによって、入力文字列にUnicodeの結合文字列が混ざることがある

  2. JavaScriptでは、String.prototype.normalize()を使ってNFCに変換すると良い


事件


事象

を含む文字列(デイリー)をrepalaceできない現象が起きました。


再現コード


index.js

const {

Transform
} = require('stream')

process.stdin
.pipe(new Transform({
transform(chunk, encoding, callback) {
callback(null, chunk.toString()
.replace('デイリー', ''))
}
}))
.pipe(process.stdout)


https://gist.github.com/ledsun/7e1fecd5214f854f68d4ca35a559845c

echo 'デイリー' | node .

な風に実行します。

デイリーが空文字に置換されます。普通は何も表示されません。

ところが、Mac OS上のfish shellで実行するとデイリーがそのまま表示されます。

不可解な事件です。


発生条件

置換条件のデイリーを少しずつ変えて、発生条件を絞り込みます。

が入ると、置換できません。


置換される


  • イリー

  • リー



置換されない


  • デイリー

  • デイ



調査内容


実際に受け取っているバイト文字列

次のプログラムを使ってバイト文字列の差を確認してみます。


index.js

process.stdin.on('readable', () => {

var chunk = process.stdin.read();
if (chunk !== null) {
console.log(chunk)
}
})

https://gist.github.com/ledsun/e1fc7e77dd688da9302648cef0086b82


結果

実際、違うのです。


Bash

<Buffer e3 83 87 e3 82 a4 e3 83 aa e3 83 bc 0a> 


fish shell

<Buffer e3 83 86 e3 82 99 e3 82 a4 e3 83 aa e3 83 bc 0a> 

何者かが一文字増えています。


何がちがうのか?

複数の文字コード方式に一括変換 | エンコード / デコード ツールを使って何が違うか確認してみましょう。


Bash

<Buffer e3 83 87 e3 82 a4 e3 83 aa e3 83 bc 0a> 


  • e3 83 87

  • e3 82 a4

  • e3 83 aa

  • e3 83 bc


fish shell

<Buffer e3 83 86 e3 82 99 e3 82 a4 e3 83 aa e3 83 bc 0a> 


  • e3 83 86

  • ? e3 82 99

  • e3 82 a4

  • e3 83 aa

  • e3 83 bc

に変わています。何者かが、一文字増えています。


何か?

u+e38299でググるとUnicode Character 'COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK' (U+3099)がでてきます。

つまりと半濁点に分かれていました。


原因

結論を言うと、一見見えているものは、がUnicodeの結合文字列として、一つに見えているものです。

は、バイト文字列としてはではありません。

はマッチしません。


対策

String.prototype.normalize()を使うと、に変換できます。として、見た目通りに、扱えます。


謝辞

原因究明にあたり、 @mysticatea 氏と @matarillo 氏にとても助けられました。

ありがとうございました。