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ę.

Risto Novik
źródło
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() + "]");
  });
obrabować
źródło
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

Madhan Ganesh
źródło
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.

Patrick.SE
źródło
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!
Wiktor Zychla
źródło
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). 
});

Arango
źródło
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.

zurfyx
źródło
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);
Diogo Cardoso
źródło
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
Miguel Mota
źródło
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!');
}
Ragnarok Ragdoll
źródło
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ą.

zoran404
źródło
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' ] ]
   */
Andrei Gătej
źródło
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()
Stefan Musarra
źródło
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' ] ]
vlc33
źródło
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();
  }
});
niksolaz
źródło
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)
}
Rohit Parte
źródło
Naprawdę powinieneś dołączyć swoje requireoświadczenie. Nie ma powodu, aby to pomijać.
solidstatejake