Windows Terminal に貼り付けた内容がおかしくなる
◆ REPL に貼り付けたコードが部分的に捨てられてる
◆ 行末まで捨てられるので 構文エラーにならない場合もある
◆ エラーが出なくて一見問題なさそうなのに問題あることがあるのでやっかい
◆ 256 文字目から行末までが捨てられてる
◆ 捨てられないケースもあって詳細はわからない
◆ スクリプトを実行した場合は 問題なく受け取れてる
◆ REPL を開始した場合でも
◆ devtools で調べた限りでは data イベントで受け取れる Buffer の時点で失われてる
◆ Windows Terminal を使う場合は 256 byte が一度に受け取る量
◆ cmd/pwsh を直接使うと 1024 byte
◆ 行末まで捨てられるので 構文エラーにならない場合もある
◆ エラーが出なくて一見問題なさそうなのに問題あることがあるのでやっかい
◆ 256 文字目から行末までが捨てられてる
◆ 捨てられないケースもあって詳細はわからない
◆ スクリプトを実行した場合は 問題なく受け取れてる
◆ REPL を開始した場合でも
◆ devtools で調べた限りでは data イベントで受け取れる Buffer の時点で失われてる
◆ Windows Terminal を使う場合は 256 byte が一度に受け取る量
◆ cmd/pwsh を直接使うと 1024 byte
プロパティアクセスで undefined になる
JSON 形式で出力したファイルから必要な部分だけを取り出したくて Node.js を使って作業していました一回限りなものなので REPL 上で適当に変換して fs.writeFileSync で出力する感じです
そういうことはよくあるので特に記事に書くようなことが起きるとは思ってなかったのですが なんかおかしいことに気づきました
あるはずのデータがないです
プロパティ名のミスかなと思いましたが 確認するとあってます
データがみつからない部分にアクセスするコードだけを実行するとちゃんと取れています
JSON データはこういうのです
[data.json]
[
{
"abcdefghijkl": "1",
"abcdefghijklmnopqrstuvwx": [
{
"abcde": { "ab": "x", "abcd": "y", "abcdefghijklmnop": "z" },
"abcdefghijkl": {
"abcde": "A"
}
}
]
}
]
require でロードして a という変数に入れます
その後に↓のコードを実行します
aa = a.map(
x => [
x.abcdefghijkl,
x.abcdefghijklmnopqrstuvwx.length,
x.abcdefghijklmnopqrstuvwx[0].abcde.ab,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcd,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcdefghijklmnop,
x.abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde,
]
)
配列の最後は "A" であるはずです
しかし
Type ".help" for more information.
> a = require("./data.json")
[ { abcdefghijkl: '1', abcdefghijklmnopqrstuvwx: [ [Object] ] } ]
> aa = a.map(
... x => [
... x.abcdefghijkl,
... x.abcdefghijklmnopqrstuvwx.length,
... x.abcdefghijklmnopqrstuvwx[0].abcde.ab,
... x.abcdefghijklmnopqrstuvwx[0].abcde.abcd,
... x.abcdefghijklmnopqrstuvwx[0].abcde.abcdefghijklmnop,
... x.abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde,
]
... )
[ [ '1', 1, 'x', 'y', 'z', undefined ] ]
>
> a[0].abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde
'A'
なぜか undefined になります
直後に直接アクセスしていて そこではちゃんととれています
起きる条件
いろいろ試していると 起きたり起きなかったりしたのですが 「a.map(...)」 のコード側を変えると発生しなくなることがありますJSON の方はアクセスでエラーが出なければ何でもよくて 元はもっと複雑なデータでしたが 上に書いたような簡単なデータでも再現できました
コードの方でも文字数が一致してれば プロパティ名を変えても大丈夫なのですが 文字数が変わると発生しなくなりました
プロパティ名の文字数はそのままでも ab へのアクセスの行を消すなどでも発生しなくなります
全体の文字数も関係してそうです
.js ファイルにコードを書いて実行する場合も発生しません
REPL に複数行書くと修正が面倒なのでエディタで書いて貼り付けていることが原因の気がします
文字数のこともあるので 同じコードを半分くらいの場所で分割して 2 回に分けてコピペしてみました
すると発生しません
Node.js なのか cmd/PowerShell なのか WindowsTerminal なのか どこの問題なのでしょうか
まぁ怪しいのは Windows Terminal ですけど
とりあえず cmd で直接実行してみると 発生しませんでした
次に PowerShell を直接使っても発生しません
Windows Terminal で確定みたいです
環境依存の可能性も考えて Windows Sandbox の中でも試してみましたが Windows Terminal を経由すると発生します
制御文字?
ただ 画面上のログでは貼り付け元と同じコードです最後の abcdefghijkl.abcde の abcde の間に見えない制御文字が挟まっていてプロパティがみつからないのでしょうか?
ですが ターミナル上のログをエディタにコピペして 文字コードを確認していっても合間に変な文字はないです
Node.js 側の入力値
Node.js が受け取ってるプロパティ名を見たいですJSON 部分のデータは何でも良かったので Proxy に置き換えることにします
ただの Proxy で console.log するよりもう少しわかりやすくしたいので こんな感じで呼び出したプロパティのパスを表示できるようにします
const log = []
const create = (path = []) => {
const p = new Proxy({}, {
get: (ctx, name, proxy) => {
const next_path = [...path, name]
log.push(next_path)
return create(next_path)
}
})
return p
}
使用例
const x = create()
x.foo
x.foo
x.bar.p1
x.bar.p2
x.baz
console.log(log)
[
["foo"],
["foo"],
["bar"],
["bar", "p1"],
["bar"],
["bar", "p2"],
["baz"]
]
アクセスされたプロパティ名が見えます
つかってみる
上のコードを使って事前にこういうコードを実行しておきますconst log = []
const create = (path = []) => {
const p = new Proxy({}, {
get: (ctx, name, proxy) => {
const next_path = [...path, name]
log.push(next_path)
return create(next_path)
}
})
return p
}
const a = [create()]
map を使っているので create() で作った要素を配列の中にいれておきます
その後にさっきと同じコードを貼り付けて実行します
aa = a.map(
x => [
x.abcdefghijkl,
x.abcdefghijklmnopqrstuvwx.length,
x.abcdefghijklmnopqrstuvwx[0].abcde.ab,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcd,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcdefghijklmnop,
x.abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde,
]
)
実行後に log を表示すると……
>
> log
[
[ 'abcdefghijkl' ],
[ 'abcdefghijklmnopqrstuvwx' ],
[ 'abcdefghijklmnopqrstuvwx', 'length' ],
[ 'abcdefghijklmnopqrstuvwx' ],
[ 'abcdefghijklmnopqrstuvwx', '0' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde', 'ab' ],
[ 'abcdefghijklmnopqrstuvwx' ],
[ 'abcdefghijklmnopqrstuvwx', '0' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde', 'abcd' ],
[ 'abcdefghijklmnopqrstuvwx' ],
[ 'abcdefghijklmnopqrstuvwx', '0' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde' ],
[ 'abcdefghijklmnopqrstuvwx', '0', 'abcde', 'abcdefghijklmnop' ],
[ 'abcdefghijklmnopqrstuv' ]
]
なんと最後の 「x.abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde」 のアクセスは 「abcdefghijklmnopqrstuv」 へのアクセスになっていました
それ以降のプロパティアクセスはないので 0 や abcdefghijkl にはアクセスされていません
そこで途切れるなら配列の 「[」 や map 関数の 「(」 の閉じカッコがなくて構文エラーになりそうなものですけど
その行だけ途中で途切れて次の行からは問題なかったということなんでしょうか
途切れた v のところまでの文字数はちょうど 256 なのでバッファ関係の影響もありそうです
256 文字だけ受け取って それ以降は次の改行から受け取りを再開してそうな動きです
無視されてそうな部分に構文エラーになりそうなコードを追加しましたが エラーは起きませんでした
aa = a.map(
x => [
x.abcdefghijkl,
x.abcdefghijklmnopqrstuvwx.length,
x.abcdefghijklmnopqrstuvwx[0].abcde.ab,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcd,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcdefghijklmnop,
x.abcdefghijklmnopqrstuvwx[0].abcdefghijkl.abcde foo bar null[1]
]
)
謎
影響大きそうな気がしますが これまで特に気づくこともなかったです標準入力をテキストに出力するようしてから同じコードをコピペしてみます
const fs = require("fs")
const ws = fs.createWriteStream("out.txt")
process.stdin.pipe(ws)
↑を実行してから貼り付けます
出力された内容は v 以降は出力されていないのかなと思ったのですが 途切れず完全にコピペされています
となると Node.js の REPL の入力値の扱いに問題がありそうな気がしますが Windows Terminal を通さなければ発生しないんですよね
ユーザーコードを経由すれば発生しないのならと node コマンドだけで REPL を開始するのではなく ↓のコードを実行して REPL を開始してみました
require("repl").start("> ")
結果は発生しませんでした
原因がわかりません
256 文字以降が捨てられてそうなので もっと試しやすくしようとこういうコードを用意しました
console.log([
"11111111111111111111",
"22222222222222222222",
"33333333333333333333",
"44444444444444444444",
"55555555555555555555",
"66666666666666666666",
"77777777777777777777",
"88888888888888888888",
"99999999999999999999",
1234567890 no errrrrror
])
配列の最後の数字の途中で 256 文字になるので 数字の途中からは捨てられるはずです
「no errrrrror」 という部分は構文エラーですが捨てられてエラーにならないはずです
しかし この場合だと構文エラーと言われます
わけがわかりません
デバッグ
Proxy の処理の途中で debugger を置いて Chrome の devtools でデバッグしてみました実行されているコードを見てみると
aa = a.map(
x => [
x.abcdefghijkl,
x.abcdefghijklmnopqrstuvwx.length,
x.abcdefghijklmnopqrstuvwx[0].abcde.ab,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcd,
x.abcdefghijklmnopqrstuvwx[0].abcde.abcdefghijklmnop,
x.abcdefghijklmnopqrstuv ]
)
となってました
内部的な line イベントでは行として v のあとに ] が来てるものになっていました
もっと元の部分を見ると stream の data イベントで入力値を受け取っていて 256 文字がまとまって受け取れてました
そこから 1 文字ずつ読み取って行と判断したら line イベントを起こすなど色々してる感じです
次の data イベントのデータをみると Buffer サイズが 6 しかないです
Buffer(6) [32, 32, 93, 13, 41, 13]
この部分です
]
)
期待する値は続きの w からなのに 改行文字も含め v の後の行末までが捨てられてます
ちなみに上の console.log にしてみたコードでは 2 回目の data イベントでちゃんと続きから受け取れています
cmd や PowerShell を直接使う場合は 256 ではなく 1024 文字が一度に受け取れるようで 今回の場合はそもそも分割されてないことが発生しない原因のようです
処理的には Node.js のコードのこの部分の処理です
https://github.com/nodejs/node/blob/v16.20.0/lib/internal/readline/emitKeypressEvents.js#L43
よくわからない
色々調べてみたもののやっぱりよくわからないとしかいえないですとりあえず Windows Terminal を避けておくのが安全なのかもしれません
また発生したバージョンは 1.16.10261.0 で 最新のプレビュー版の 1.18.1462.0 でも変わらず発生してます
ただ直接 PowerShell を使うと発生してない理由は 一度に受け取る文字が多いことなので 1024 文字以上を貼り付けると発生する可能性はあります
Windows Terminal で 256 文字以上でも発生しないこともあるので PowerShell で 1024 文字以上で試して発生してなくても すべての場合に安全とは言えないです
REPL に貼り付けをしないのが一番ですが それは利便性的にかなりつらいです
どうにか対処されてほしいものです