LoginSignup
11
9

More than 3 years have passed since last update.

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

Last updated at Posted at 2016-10-05

最初に結論

  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)

echo 'デイリー' | node .

な風に実行します。

デイリーが空文字に置換されます。普通は何も表示されません。
ところが、Mac OS上のfish shellで実行するとデイリーがそのまま表示されます。

不可解な事件です。

発生条件

置換条件のデイリーを少しずつ変えて、発生条件を絞り込みます。
が入ると、置換できません。

置換される

  • イリー
  • リー

置換されない

  • デイリー
  • デイ

調査内容

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

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

index.js
process.stdin.on('readable', () => {
  var chunk = process.stdin.read();
  if (chunk !== null) {
    console.log(chunk)
  }
})

結果

実際、違うのです。

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 氏にとても助けられました。

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

11
9
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9