Читання цінності з консолі, інтерактивно


155

Я думав зробити простий сервер http-сервера з деяким розширенням консолі. Я знайшов фрагмент для читання з даних командного рядка.

  var i = rl.createInterface(process.stdin, process.stdout, null);
  i.question('Write your name: ', function(answer) {
    console.log('Nice to meet you> ' + answer);
    i.close();
    process.stdin.destroy();

  });

добре задавати питання неодноразово, я не можу просто використовувати while(done) { }цикл? Також добре, якщо сервер отримує вихід на час запитання, він руйнує рядок.


5
Я припускаю, що rlви маєте на увазі читальну лінію ?
jpaugh

Ви можете використовувати інтерфейс, що не блокує, як той, що використовується у цій відповіді , тоді ви можете зробити while(done)цикл.
Кейван

Відповіді:


182

ви не можете робити цикл "while (done)", оскільки це вимагатиме блокування на вході, що node.js не любить робити.

Замість цього встановіть зворотний дзвінок, який потрібно дзвонити кожен раз, коли щось вводиться:

var stdin = process.openStdin();

stdin.addListener("data", function(d) {
    // note:  d is an object, and when converted to a string it will
    // end with a linefeed.  so we (rather crudely) account for that  
    // with toString() and then trim() 
    console.log("you entered: [" + 
        d.toString().trim() + "]");
  });

2
Дякую, що це працює, чи дозволяє слухач "кінця" зателефонувати на деякі операції закриття і сказати "До побачення"?
Рісто Новік

Я видалив слухача з кінця з прикладу, я не знаю, де це буде корисно, щоб бути чесним.
пограбувати

2
Ви можете спростити вихідний рядок до d.toString (). Trim ()
MKN Web Solutions

6
Ця відповідь датується 2011 роком і багато з того змінилося. Зокрема, найперша частина відповіді: цикл , який ти не можеш робити, ... вже не тримає. Так, ви можете мати певний цикл і все одно не блокувати, завдяки шаблону async-wait. Інші відповіді це відображають. Кожен, хто сьогодні читає це - будь ласка, зверніться до інших відповідей.
Wiktor Zychla

1
Щоб продовжити @WiktorZychla, функція process.openStdin, поки вона ще працює, була застаріла близько 2011 року, і ви не знайдете жодної документації щодо неї.
calder.ty

111

Я використовував для цього інший API.

var readline = require('readline');
var rl = readline.createInterface(process.stdin, process.stdout);
rl.setPrompt('guess> ');
rl.prompt();
rl.on('line', function(line) {
    if (line === "right") rl.close();
    rl.prompt();
}).on('close',function(){
    process.exit(0);
});

Це дозволяє підказувати цикл, поки відповідь не буде right. Також це дає гарну маленьку консоль. Ви можете знайти деталі @ http://nodejs.org/api/readline.html#readline_example_tiny_cli


11
Це чудова відповідь. Що може бути не очевидним (але це великий плюс) - це те, що readline не є зовнішньою залежністю: це частина node.js.
jlh

51

API Readline змінився досить сильно з 12 '. Документ покаже корисний приклад для збору даних користувачів зі стандартного потоку:

const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

rl.question('What do you think of Node.js? ', (answer) => {
  console.log('Thank you for your valuable feedback:', answer);
  rl.close();
});

Більше інформації тут.


5
це лише основний приклад. Як ви взаємодієте? питання / відповідь? багаторазовий вибір тощо? Як знову відкрити rl після закриття, якщо не вдається, як працювати з відкритим rl, щоб взаємодіяти з користувачем, включаючи певну логіку
Pawel Cioch

28

Я вважаю, що це заслуговує на сучасну async-awaitвідповідь, якщо використовувати вузол> = 7.x.

Відповідь все ще використовується, ReadLine::questionале обгортає її так, щоб це while (done) {}було можливим, про що ОП чітко запитує.

var cl = readln.createInterface( process.stdin, process.stdout );
var question = function(q) {
    return new Promise( (res, rej) => {
        cl.question( q, answer => {
            res(answer);
        })
    });
};

а потім приклад використання

(async function main() {
    var answer;
    while ( answer != 'yes' ) {
        answer = await question('Are you sure? ');
    }
    console.log( 'finally you are sure!');
})();

веде до наступної розмови

Are you sure? no
Are you sure? no
Are you sure? yes
finally you are sure!

Це саме та відповідь, яку я шукав. Я думаю, що це має бути найкращим.
Вільям Чоу

Гарний. Для великих сценаріїв потрібна асинхронізація. Це саме те, що мені було потрібно.
Абхай Широ

25

Будь ласка, використовуйте readline-sync , це дозволяє вам працювати з синхронною консоллю без зворотних зворотних дзвінків. Навіть працює з паролями:

var favFood = read.question('What is your favorite food? ', {
  hideEchoBack: true // The typed text on screen is hidden by `*` (default). 
});


5
Це вимагає додаткової залежності, тому я віддаю перевагу іншим рішенням.
Рісто Новік

Не працює на SO "Uncaught ReferenceError: read не визначено"
awwsmm

12

@rob відповідь буде працювати в більшості випадків, але може не спрацювати, як ви очікуєте, при тривалих введеннях.

Це те, що вам слід використовувати замість цього:

const stdin = process.openStdin();
let content = '';

stdin.addListener('data', d => {
  content += d.toString();
});

stdin.addListener('end', () => {
  console.info(`Input: ${content}`);
});

Пояснення, чому це рішення працює:

addListener('data') працює як буфер, зворотний виклик буде викликаний, коли він заповнений або / і його кінець вводу.

Що з довгими входами? Самотній'data' зворотного дзвінка буде недостатньо, отже, ви отримаєте вхідне розділення на дві або більше частин. Це часто не зручно.

addListener('end')повідомлять нас, коли читач stdin закінчиться, читаючи наші дані. Оскільки ми зберігали попередні дані, тепер ми можемо їх читати та обробляти разом.


3
коли я використовую код вище і вставляю деякий вхід, а потім клавішу "Enter" консолі, продовжуйте просити більше вводу. як ми повинні це припинити?
Матан Тубул

5

Я рекомендую використовувати Inquirer , оскільки він пропонує набір загальних інтерфейсів користувальницького командного рядка.

const inquirer = require('inquirer');

const questions = [{
  type: 'input',
  name: 'name',
  message: "What's your name?",
}];

const answers = await inquirer.prompt(questions);
console.log(answers);


3

Це надскладно. Простіша версія:

var rl = require('readline');
rl.createInterface... etc

було б у використанні

var rl = require('readline-sync');

тоді він буде чекати, коли ви використовуєте

rl.question('string');

то простіше повторити. наприклад:

var rl = require('readline-sync');
for(let i=0;i<10;i++) {
    var ans = rl.question('What\'s your favourite food?');
    console.log('I like '+ans+' too!');
}

2

Поширений випадок використання, ймовірно, буде для програми відображати загальну підказку та обробляти її в операторі комутатора.

Ви можете отримати поведінку, еквівалентну циклу в той час, використовуючи помічну функцію, яка б викликала себе у зворотному дзвінку:

const readline = require('readline');
const rl = readline.createInterface(process.stdin, process.stdout);

function promptInput (prompt, handler)
{
    rl.question(prompt, input =>
    {
        if (handler(input) !== false)
        {
            promptInput(prompt, handler);
        }
        else
        {
            rl.close();
        }
    });
}

promptInput('app> ', input =>
{
    switch (input)
    {
        case 'my command':
            // handle this command
            break;
        case 'exit':
            console.log('Bye!');
            return false;
    }
});

Ви можете пропустити порожню рядок замість того, 'app> 'якщо ваша програма вже щось надрукує на екрані поза цим циклом.


2

Мій підхід до цього полягає у використанні генераторів асинхронізації .

Припустимо, у вас є масив питань:

 const questions = [
        "How are you today ?",
        "What are you working on ?",
        "What do you think of async generators ?",
    ]

Для того, щоб використовувати awaitключове слово, ви повинні перетворити програму в асинхронний IIFE.

(async () => {

    questions[Symbol.asyncIterator] = async function * () {
        const stdin = process.openStdin()

        for (const q of this) {
            // The promise won't be solved until you type something
            const res = await new Promise((resolve, reject) => {
                console.log(q)

                stdin.addListener('data', data => {
                    resolve(data.toString())
                    reject('err')
                });
            })

            yield [q, res];
        }

    };

    for await (const res of questions) {
        console.log(res)
    }

    process.exit(0)
})();

Очікувані результати:

How are you today ?
good
[ 'How are you today ?', 'good\n' ]
What are you working on ?
:)
[ 'What are you working on ?', ':)\n' ]
What do you think about async generators ?
awesome
[ 'What do you think about async generators ?', 'awesome\n' ]

Якщо ви хочете отримати відповіді на питання взагалі, ви можете досягти цього простою модифікацією:

const questionsAndAnswers = [];

    for await (const res of questions) {
        // console.log(res)
        questionsAndAnswers.push(res)
    }

    console.log(questionsAndAnswers)

   /*
     [ [ 'How are you today ?', 'good\n' ],
     [ 'What are you working on ?', ':)\n' ],
     [ 'What do you think about async generators ?', 'awesome\n' ] ]
   */

2

Мені довелося написати гру «tic-tac-toe» в Node, яка приймала введення з командного рядка, і написала цей базовий блок коду async / очікування, який зробив трюк.

const readline = require('readline')

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

async function getAnswer (prompt) {
  const answer = await new Promise((resolve, reject) =>{
    rl.question(`${prompt}\n`, (answer) => {
      resolve(answer)
    });
  })
  return answer
}

let done = false
const playGame = async () => {
  let i = 1
  let prompt = `Question #${i}, enter "q" to quit`
  while (!done) {
    i += 1
    const answer = await getAnswer(prompt)
    console.log(`${answer}`)
    prompt = processAnswer(answer, i)
  }
  rl.close()
}

const processAnswer = (answer, i) => {
  // this will be set depending on the answer
  let prompt = `Question #${i}, enter "q" to quit`
  // if answer === 'q', then quit
  if (answer === 'q') {
    console.log('User entered q to quit')
    done = true
    return
  }
  // parse answer

  // if answer is invalid, return new prompt to reenter

  // if answer is valid, process next move

  // create next prompt
  return prompt
}

playGame()

1

Блокування поведінки розблокованої лінії читання

Уявіть, що у вас є три питання, на які потрібно відповісти з консолі, оскільки тепер ви знаєте, що цей код не запуститься, оскільки стандартний модуль readline має "розблоковану" поведінку.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

function askaquestion(question) {
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(question[0], function(answer) {
    console.log(answer);
    question[1] = answer;
    rl.close();
  });
};

var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i]);
}

console.log('Results:',questionaire );

Виконаний вихід:

node test.js
Third Question: Results: [ [ 'First Question: ', '' ],
  [ 'Second Question: ', '' ],
  [ 'Third Question: ', '' ] ]        <--- the last question remain unoverwritten and then the final line of the program is shown as the threads were running waiting for answers (see below)
aaa        <--- I responded with a single 'a' that was sweeped by 3 running threads
a        <--- Response of one thread

a        <--- Response of another thread

a        <--- Response of another thread (there is no order on threads exit)

Пропоноване рішення використовує випромінювач події для передачі сигналу про закінчення розблокуючої нитки і включає в себе функцію слухача та логіку циклу та кінець програми.

'use strict';

var questionaire=[['First Question: ',''],['Second Question: ',''],['Third Question: ','']];

// Introduce EventEmitter object
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {};

const myEmitter = new MyEmitter();
myEmitter.on('continue', () => {
  console.log('continue...');
  i++; if (i< questionaire.length) askaquestion(questionaire[i],myEmitter);    // add here relevant loop logic
           else console.log('end of loop!\nResults:',questionaire );
});
//

function askaquestion(p_question,p_my_Emitter) { // add a parameter to include my_Emitter
const readline = require('readline');

const rl = readline.createInterface(
    {input: process.stdin, output:process.stdout}
    );
  rl.question(p_question[0], function(answer) {
    console.log(answer);
    p_question[1] = answer;
    rl.close();
    myEmitter.emit('continue');    // Emit 'continue' event after the question was responded (detect end of unblocking thread)
  });
};

/*var i=0;  
for (i=0; i < questionaire.length; i++) {
askaquestion(questionaire[i],myEmitter);
}*/

var i=0;
askaquestion(questionaire[0],myEmitter);        // entry point to the blocking loop


// console.log('Results:',questionaire )    <- moved to the truly end of the program

Виконаний вихід:

node test2.js
First Question: 1
1
continue...
Second Question: 2
2
continue...
Third Question: 3
3
continue...
done!
Results: [ [ 'First Question: ', '1' ],
  [ 'Second Question: ', '2' ],
  [ 'Third Question: ', '3' ] ]

0

Я створив невеликий сценарій для каталогу читання і записав назву консолі новий файл (наприклад: 'name.txt') та текст у файл.

const readline = require('readline');
const fs = require('fs');

const pathFile = fs.readdirSync('.');

const file = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

file.question('Insert name of your file? ', (f) => {
  console.log('File is: ',f.toString().trim());
  try{
    file.question('Insert text of your file? ', (d) => {
      console.log('Text is: ',d.toString().trim());
      try {
        if(f != ''){
          if (fs.existsSync(f)) {
            //file exists
            console.log('file exist');
            return file.close();
          }else{
            //save file
            fs.writeFile(f, d, (err) => {
                if (err) throw err;
                console.log('The file has been saved!');
                file.close();
            });
          }
        }else{
          //file empty 
          console.log('Not file is created!');
          console.log(pathFile);
          file.close();
        }
      } catch(err) {
        console.error(err);
        file.close();
      }
    });
  }catch(err){
    console.log(err);
    file.close();
  }
});

0

Найпростіший спосіб - використовувати синхронізацію читання ліній

Він обробляє один за одним вхід і вихід.

npm i readline-sync

наприклад:

var firstPrompt = readlineSync.question('Are you sure want to initialize new db? This will drop whole database and create new one, Enter: (yes/no) ');

if (firstPrompt === 'yes') {
    console.log('--firstPrompt--', firstPrompt)
    startProcess()
} else if (firstPrompt === 'no') {
    var secondPrompt = readlineSync.question('Do you want to modify migration?, Enter: (yes/no) ');
    console.log('secondPrompt ', secondPrompt)
    startAnother()
} else {
    console.log('Invalid Input')
    process.exit(0)
}

Ви дійсно повинні включити свою requireзаяву. Немає підстав залишати це поза межами.
solidstatejake
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.