Menu

Node.js readlineでターミナルから入力を受け取る

Node.jsにはprompt()関数がありません。ターミナルでユーザーに質問するには、process.stdinにreadlineのインターフェースを作ってquestion()を呼ぶか、readline/promisesでawaitします。

このページのコードはエディタで実行できます。編集してすぐに結果を確認できます。

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 を使います。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める