Node.jsでユーザーの入力を読むには、process.stdin に readline のインターフェースを作り、rl.question() で質問します。Node.jsには prompt() がありません。ブラウザ版はダイアログを開きますが、ターミナルのプログラムにはダイアログがないからです。今どきの書き方は readline/promises を使うもので、await rl.question('Name? ') がユーザーの入力した行を返します。終わったらインターフェースを閉じてください。閉じないと、プログラムは入力を待ち続けます。
question() はプロンプトを表示し、1行の入力を待ち、改行を除いた文字列を返しました。そのためプログラムは Next year you will be 37. と表示しました。質問が複数あるなら、question() を1つずつ順にawaitします。
コールバック版のreadline question()
どのNode.jsバージョンにもある元のAPIは、答えをコールバックに渡します。質問を重ねると、コールバックも入れ子になります。
プロンプトが表示され、続いて You picked blue. と表示されました。答えはただの文字列なので、比較する前にtrimしましょう。単語の後にスペースを打ったユーザーの答えは 'blue ' になり、'blue' とは等しくなりません。
1行ずつ入力を読む
入力が終わるまですべての行を処理するには、インターフェースを for await で回します。ユーザーが打ち込むリスト(Ctrl+Dで終了)や、node index.js < numbers.txt でパイプしたファイルを読むときの方法です。古いコードは同じことをイベントで書きます。各行に rl.on('line', fn)、最後に rl.on('close', fn) です。このループは、その2つのイベントを上から下へ書いたものです。
3 numbers, total 49 と表示され、入力が終わるとループは自然に終了したので、close() は不要です。4行目の入力として 1 を足すと、結果は 4 numbers, total 50 になります。
stdinを一度にすべて読む
別のプログラムからパイプされた入力(cat data.txt | node index.js)なら、1回の呼び出しで全部読むほうが簡単です。ファイルディスクリプタ 0 が標準入力です。Windowsで保存されたファイルは行末が \r\n のことがあるので、そういう入力が来る可能性があるなら /\r?\n/ で分割しましょう。
3つの品目が列にそろって表示されました。readFileSync(0) は入力が終わるまでブロックします。パイプを読むスクリプトやテストなら問題ありませんが、対話型のツールには向きません。ユーザーはプロンプトを1つも見ないうちに、すべてを入力しなければならなくなるからです。
どれを使うか
4つとも同じ標準入力を読みます。違うのは、コードがテキストを受け取るタイミングです。答え1つごと、1行ごと、あるいは最後にまとめて受け取ります。
| やりたいこと | 使うもの |
|---|---|
| いくつかの質問を順に聞く | readline/promises と await rl.question() |
| 17より前のNode.jsにも対応する | readline と question() のコールバック |
| 届いた行を1つずつ処理する | for await (const line of rl) |
| パイプやファイルをstdinから丸ごと読む | fs.readFileSync(0, 'utf8') |
| 値がプログラムの開始前にわかっている | 代わりにprocess.argv |
よくある間違い
rl.close() を忘れる。 インターフェースが標準入力を開いたままにするので、最後の質問の後もプログラムが終了しません。答えがそろったら閉じましょう。
答えを数値として使う。 ターミナルで打ち込まれたものはすべてそうですが、答えは文字列です。'36' + 1 は '361' になるので、先に Number() で変換します。
複数の答えをパイプで複数の質問に流し込む。 入力がキーボードではなくパイプから来ると、readline は全部を一度に読み、対応する question() が聞かれる前に届いた行は捨てられます。パイプの入力には for await のループか readFileSync(0) を使いましょう。
output オプションを省く。 output: process.stdout がないと question() はプロンプトを表示せず、ユーザーは空のターミナルを見つめることになります。1行ずつ読むループは何も表示しないので、これは不要です。
よくある質問
Node.jsでpromptが未定義になるのはなぜですか?
prompt() はダイアログを開くブラウザの関数で、Node.jsにはそれを開くウィンドウがありません。Node.jsでは readline を使うか、メニューやパスワード入力欄が欲しいなら prompts や inquirer のようなパッケージを使います。
パスワードを表示せずに読むには?
readline は押したキーをすべて表示します。prompts や @inquirer/password のようなパッケージは入力を伏せてくれます。自分でやるなら、process.stdin.setRawMode(true) でターミナルをrawモードにして、キーを1つずつ処理することになります。
readlineとprocess.argvのどちらを使うべきですか?
値がプログラムの開始前にわかっているなら process.argv を使います。人がいなくてもスクリプトから実行できるからです。質問を1つずつ続けて聞く対話型のツールには readline を使います。