Interaktywne odczytywanie wartości z konsoli


155

Pomyślałem o stworzeniu prostego serwera HTTP z rozszerzeniem konsoli. Znalazłem fragment do odczytania z danych wiersza poleceń.

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

  });

dobrze zadawać pytania wielokrotnie, nie mogę po prostu użyć while(done) { }pętli? Również dobrze, jeśli serwer otrzyma dane wyjściowe w czasie pytania, zrujnuje linię.


5
Zakładam, że rlmasz na myśli readline ?
jpaugh

Możesz użyć nieblokującego interfejsu, takiego jak ten użyty w tej odpowiedzi , a następnie możesz wykonać while(done)pętlę.
Keyvan

Odpowiedzi:


182

nie możesz zrobić pętli "while (done)", ponieważ wymagałoby to blokowania wejścia, czego node.js nie lubi.

Zamiast tego skonfiguruj wywołanie zwrotne, które będzie wywoływane za każdym razem, gdy coś zostanie wprowadzone:

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
Dziękuję, że działa, czy słuchacz „końca” pozwala wywołać jakieś operacje zamykające i powiedzieć „Do widzenia”?
Risto Novik

Usunąłem słuchacza „końcowego” z przykładu, nie wiem, gdzie naprawdę się przyda, jeśli mam być szczery.
obrabować

2
Możesz uprościć wyjściowy ciąg znaków do d.toString (). Trim ()
MKN Web Solutions

6
Ta odpowiedź pochodzi z 2011 roku i od tego czasu wiele się zmieniło. W szczególności pierwsza część odpowiedzi, że nie można zrobić pętli while ... już się nie trzyma. Tak, możesz mieć pętlę while i nadal nie blokować, dzięki wzorcowi async-await. Inne odpowiedzi to odzwierciedlają. Do każdego, kto to czyta w dzisiejszych czasach - prosimy o zapoznanie się również z innymi odpowiedziami.
Wiktor Zychla

1
Kontynuując @WiktorZychla, funkcja process.openStdin, podczas gdy nadal działała, została wycofana około 2011 roku i nie znajdziesz żadnej dokumentacji na jej temat.
calder.ty

111

W tym celu użyłem innego 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);
});

Pozwala to na podpowiadanie w pętli, aż odpowiedź będzie right. Daje też fajną małą konsolę. Szczegóły można znaleźć @ http://nodejs.org/api/readline.html#readline_example_tiny_cli


11
To świetna odpowiedź. Co może nie być oczywiste (ale jest dużym plusem), to fakt, że readline nie jest zewnętrzną zależnością: jest częścią node.js.
jlh

51

Readline API zmieniło się nieco od 12 roku. Dokumenty pokazują przydatny przykład przechwytywania danych wejściowych użytkownika ze standardowego strumienia:

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

Więcej informacji tutaj.


5
to tylko podstawowy przykład. Jak się zachowujesz? pytanie odpowiedź? wielokrotny wybór i tym podobne? Jak ponownie otworzyć rl po zamknięciu, jeśli nie możesz, jak pracować z otwartym rl, aby współdziałać z użytkownikiem, w tym trochę logiki
Paweł Cioch

27

Uważam, że zasługuje to na nowoczesną async-awaitodpowiedź, zakładając, że używany jest węzeł> = 7.x.

Odpowiedź nadal używa, ReadLine::questionale zawija ją tak, że while (done) {}jest możliwe, o co PO wyraźnie pyta.

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

a następnie przykład użycia

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

prowadzi do następującej rozmowy

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

To jest dokładnie odpowiedź, której szukałem. Myślę, że powinien być najlepszy.
William Chou

Piękny. W przypadku większych skryptów wymagane jest oczekiwanie asynchroniczne. Właśnie tego potrzebowałem.
Abhay Shiro

25

Użyj readline-sync , pozwala to pracować z konsolą synchroniczną bez piekieł wywołań zwrotnych. Działa nawet z hasłami:

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


5
Wymaga to dodatkowej zależności, więc wolałbym inne rozwiązania.
Risto Novik

Nie działa na SO „Uncaught ReferenceError: read is not
specified

12

@rob answer będzie działać przez większość czasu, ale może nie działać zgodnie z oczekiwaniami w przypadku długich danych wejściowych.

Oto, czego powinieneś używać zamiast tego:

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

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

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

Wyjaśnienie, dlaczego to rozwiązanie działa:

addListener('data') działa jak bufor, wywołanie zwrotne zostanie wywołane, gdy będzie pełny lub / i nastąpi koniec wejścia.

A co z długimi wejściami? Pojedyncze 'data'wywołanie zwrotne nie wystarczy, dlatego dane wejściowe zostaną podzielone na dwie lub więcej części. To często nie jest wygodne.

addListener('end')powiadomi nas, gdy czytnik stdin skończy odczytywać nasze dane wejściowe. Ponieważ zapisywaliśmy poprzednie dane, możemy je teraz odczytywać i przetwarzać razem.


3
kiedy używam powyższego kodu i wstawiam dane wejściowe, a następnie klawisz „Enter”, konsola ciągle prosi mnie o dodatkowe dane. jak powinniśmy to zakończyć?
Matan Tubul

5

Polecam korzystanie z Inquirer , ponieważ zawiera zbiór typowych interaktywnych interfejsów użytkownika wiersza poleceń.

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

Oto przykład:

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

Wynik:

Enter name: bob
Your name is: bob

Miła odpowiedź bracie !! Po prostu proste i jasne.
MD JULHAS HOSSAIN,

3

To jest zbyt skomplikowane. Łatwiejsza wersja:

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

byłoby użyć

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

wtedy będzie czekać, kiedy użyjesz

rl.question('string');

wtedy łatwiej jest powtórzyć. na przykład:

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

Typowym przypadkiem użycia byłoby prawdopodobnie wyświetlenie przez aplikację ogólnego monitu i obsłużenie go w instrukcji switch.

Możesz uzyskać zachowanie równoważne pętli while, używając funkcji pomocniczej, która wywoła się w wywołaniu zwrotnym:

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

Możesz przekazać pusty ciąg zamiast, 'app> 'jeśli Twoja aplikacja już drukuje coś na ekranie poza tą pętlą.


2

Moje podejście do tego polegałoby na użyciu generatorów asynchronicznych .

Zakładając, że masz szereg pytań:

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

Aby użyć awaitsłowa kluczowego, musisz opakować swój program w asynchroniczne 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)
})();

Oczekiwane rezultaty:

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' ]

Jeśli chcesz uzyskać wszystkie pytania i odpowiedzi, możesz to osiągnąć za pomocą prostej modyfikacji:

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

Musiałem napisać grę „kółko i krzyżyk” w Node, która pobierała dane wejściowe z wiersza poleceń i napisała podstawowy blok kodu async / await, który załatwił sprawę.

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

Blokowanie niezablokowanego zachowania readline

Wyobraź sobie, że masz trzy pytania, na które musisz odpowiedzieć z konsoli, ponieważ teraz wiesz, że ten kod nie zostanie uruchomiony, ponieważ standardowy moduł readline ma zachowanie 'odblokowane', co oznacza, że ​​każde pytanie rl.question jest niezależnym wątkiem, więc ten kod nie będzie działać.

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

Wydajność robocza:

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)

Proponowane rozwiązanie wykorzystuje emiter zdarzeń do sygnalizowania końca odblokowującego wątku oraz włącza logikę pętli i koniec programu do jego funkcji nasłuchującej.

'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

Wydajność robocza:

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

Pozyskałem mały skrypt do odczytu katalogu i zapisania nazwy konsoli nowego pliku (przykład: „nazwa.txt”) i tekstu do pliku.

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

Najłatwiejszym sposobem jest użycie readline-sync

Przetwarza jedno po drugim wejście i wyjście.

npm i readline-sync

na przykład:

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

Naprawdę powinieneś dołączyć swoje requireoświadczenie. Nie ma powodu, aby to pomijać.
solidstatejake
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.