Чтение значения из консоли, в интерактивном режиме


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вы имеете в виду readline ?
jpaugh

Вы можете использовать неблокирующий интерфейс, подобный тому, который использовался в этом ответе , тогда вы можете сделать while(done)цикл.
Keyvan

Ответы:


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 годом, и с тех пор многое изменилось. В частности, самая первая часть ответа, вы не можете сделать цикл while ... больше не выполняется. Да, вы можете иметь цикл while и по-прежнему не блокировать, благодаря шаблону async-await. Другие ответы отражают это. Для тех, кто читает это в настоящее время - пожалуйста, обратитесь к другим ответам.
Виктор Зихла

1
Следуя @WiktorZychla, функция process.openStdin, все еще работающая, была объявлена ​​устаревшей около 2011 года, и вы не найдете никакой документации по этому поводу.
calder.ty

112

Я использовал другой 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 после закрытия, если не получается, как работать с open 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
Это требует дополнительной зависимости, поэтому я бы предпочел другие решения.
Risto Novik

Не запускается на SO "Uncaught ReferenceError: чтение не определено"
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')уведомит нас, когда читатель стандартного ввода закончит чтение нашего ввода. Поскольку мы хранили предыдущие данные, теперь мы можем читать и обрабатывать все это вместе.


3
Когда я использую приведенный выше код и вставляю какой-то ввод, а затем нажимаю клавишу «ввод», консоль продолжает просить меня ввести больше. как мы можем прекратить это?
Матан Тубул

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);

5

Вот пример:

const stdin = process.openStdin()

process.stdout.write('Enter name: ')

stdin.addListener('data', text => {
  const name = text.toString().trim()
  console.log('Your name is: ' + name)

  stdin.pause() // stop reading
})

Вывод:

Enter name: bob
Your name is: bob

Хороший ответ, брат !! Просто и понятно.
MD.JULHAS HOSSAIN

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

Вероятно, в общем случае приложение будет отображать универсальное приглашение и обрабатывать его в операторе switch.

Вы можете получить поведение, эквивалентное циклу while, используя вспомогательную функцию, которая будет вызывать себя в обратном вызове:

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

Я должен был написать игру «крестики-нолики» в Node, которая принимала данные из командной строки, и написал этот базовый блок кода async / await, который добился цели.

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 имеет «разблокированное» поведение, в котором каждый rl.question является независимым потоком, поэтому этот код не будет выполняться.

'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

Самый простой способ - использовать readline-sync

Он обрабатывает один за другим вход и выход.

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.