Lecture de la valeur depuis la console, de manière interactive

155

J'ai pensé créer un serveur http simple avec une extension de console. J'ai trouvé l'extrait à lire à partir des données de ligne de commande.

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

  });

Eh bien, pour poser les questions à plusieurs reprises, je ne peux pas simplement utiliser le while(done) { } boucle? Aussi bien si le serveur reçoit la sortie au moment des questions, cela ruine la ligne.

Risto Novik
la source
5
Je suppose que rlvous voulez dire readline ?
jpaugh
Vous pouvez utiliser une interface non bloquante comme celle utilisée dans cette réponse , puis vous pouvez faire une while(done)boucle.
Keyvan

Réponses:

182

vous ne pouvez pas faire une boucle "while (done)" car cela nécessiterait un blocage en entrée, ce que node.js n'aime pas faire.

Au lieu de cela, configurez un rappel à appeler chaque fois que quelque chose est entré:

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() + "]");
  });
Rob
la source
2
Merci cela fonctionne, est-ce que l'auditeur "de fin" permet d'appeler certaines opérations de clôture et de dire "Au revoir"?
Risto Novik
J'ai supprimé l'auditeur "final" de l'exemple, je ne sais pas où il sera vraiment utile d'être honnête.
rob
2
Vous pouvez simplifier la sortie de la chaîne en d.toString (). Trim ()
MKN Web Solutions
6
Cette réponse date de 2011 et beaucoup de choses ont changé depuis. En particulier, la toute première partie de la réponse, vous ne pouvez pas faire une boucle while ... ne tient plus. Oui, vous pouvez avoir une boucle while et ne pas bloquer, grâce au modèle async-await. D'autres réponses reflètent cela. À tous ceux qui lisent ceci de nos jours - veuillez également consulter d'autres réponses.
Wiktor Zychla
1
A suivre sur @WiktorZychla, la fonction process.openStdin alors qu'elle fonctionnait toujours, a été déconseillée vers 2011, et vous ne trouverez aucune documentation la concernant.
calder.ty
112

J'ai utilisé une autre API à cette fin.

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

Cela permet d'inviter en boucle jusqu'à ce que la réponse soit right. En outre, cela donne une jolie petite console.Vous pouvez trouver les détails @ http://nodejs.org/api/readline.html#readline_example_tiny_cli

Madhan Ganesh
la source
11
C'est une excellente réponse. Ce qui n'est peut-être pas évident (mais c'est un gros plus), c'est que readline n'est pas une dépendance externe: il fait partie de node.js.
jlh
51

L'API Readline a beaucoup changé depuis 12 '. Les documents montrent un exemple utile pour capturer les entrées utilisateur à partir d'un flux standard:

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

Plus d'informations ici.

Patrick.SE
la source
5
ce n'est qu'un exemple basique. Comment interagissez-vous? Question Réponse? choix multiples et autres? Comment rouvrir rl une fois fermé, si vous ne pouvez pas comment travailler avec open rl pour interagir avec l'utilisateur, y compris un peu de logique
Pawel Cioch
28

Je pense que cela mérite une async-awaitréponse moderne , en supposant que le nœud> = 7.x est utilisé.

La réponse l'utilise toujours ReadLine::questionmais l'enveloppe pour que le while (done) {}soit possible, ce que l'OP demande explicitement.

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

puis un exemple d'utilisation

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

mène à la conversation suivante

Are you sure? no
Are you sure? no
Are you sure? yes
finally you are sure!
Wiktor Zychla
la source
C'est exactement la réponse que je cherchais. Je pense que ce devrait être le premier.
William Chou
Belle. L'attente asynchrone est nécessaire pour les scripts plus volumineux. C'est exactement ce dont j'avais besoin.
Abhay Shiro
25

Veuillez utiliser readline-sync , cela vous permet de travailler avec une console synchrone sans enfer de callbacks. Fonctionne même avec des mots de passe:

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

Arango
la source
5
Cela nécessite une dépendance supplémentaire, je préférerais donc d'autres solutions.
Risto Novik
Ne fonctionne pas sur SO "Uncaught ReferenceError: read is not defined"
awwsmm
12

@rob answer fonctionnera la plupart du temps, mais il se peut que cela ne fonctionne pas comme prévu avec de longues entrées.

C'est ce que vous devriez utiliser à la place:

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

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

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

Explication des raisons pour lesquelles cette solution fonctionne:

addListener('data') fonctionne comme un tampon, le callback sera appelé quand il est plein ou / et sa fin d'entrée.

Qu'en est-il des entrées longues? Un seul 'data'rappel ne suffira pas, vous obtiendrez donc votre entrée divisée en deux ou plusieurs parties. Ce n'est souvent pas pratique.

addListener('end')nous avertira lorsque le lecteur stdin aura fini de lire notre entrée. Depuis que nous stockons les données précédentes, nous pouvons maintenant les lire et les traiter toutes ensemble.

zurfyx
la source
3
lorsque j'utilise le code ci-dessus et que j'insère une entrée, puis la touche "Entrée", la console continue de me demander plus d'entrée. comment devrions-nous y mettre fin?
Matan Tubul
5

Je recommande d'utiliser Inquirer , car il fournit une collection d'interfaces utilisateur de ligne de commande interactives communes.

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
la source
5

Voici un exemple:

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

Production:

Enter name: bob
Your name is: bob
Miguel Mota
la source
Belle réponse frère !! Simplement simple et clair.
MD.JULHAS HOSSAIN
3

C'est trop compliqué. Une version plus simple de:

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

serait utiliser

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

alors il attendra quand vous utiliserez

rl.question('string');

alors il est plus facile de répéter. par exemple:

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!');
}
Ragdoll Ragnarok
la source
2

Un cas d'utilisation courant serait probablement que l'application affiche une invite générique et la gère dans une instruction switch.

Vous pouvez obtenir un comportement équivalent à une boucle while en utilisant une fonction d'assistance qui s'appellerait elle-même dans le rappel:

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

Vous pouvez passer une chaîne vide au lieu de 'app> 'si votre application imprime déjà quelque chose à l'écran en dehors de cette boucle.

zoran404
la source
2

Mon approche serait d'utiliser des générateurs asynchrones .

En supposant que vous ayez un éventail de questions:

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

Pour utiliser le awaitmot - clé, vous devez envelopper votre programme dans un IIFE asynchrone.

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

Résultats attendus:

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

Si vous souhaitez obtenir des questions et des réponses, vous pouvez y parvenir avec une simple modification:

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
la source
2

J'ai dû écrire un jeu "tic-tac-toe" dans Node qui prenait les entrées de la ligne de commande, et j'ai écrit ce bloc de code de base async / await qui a fait l'affaire.

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
la source
1

Blocage du comportement débloqué de readline

Imaginez que vous ayez trois questions auxquelles il faut répondre depuis la console, car vous savez maintenant que ce code ne fonctionnera pas car le module standard de readline a un comportement `` débloqué '', disons que chaque rl.question est un thread indépendant, donc ce code ne fonctionnera pas.

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

Sortie en cours d'exécution:

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)

La solution proposée utilise un émetteur d'événements pour signaler la fin d'un thread de déblocage, et inclut la logique de boucle et la fin du programme dans sa fonction d'écoute.

'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

Sortie en cours d'exécution:

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
la source
0

J'ai créé un petit script pour lire le répertoire et écrire un nouveau fichier de nom de console (exemple: 'name.txt') et du texte dans un fichier.

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
la source
0

Le moyen le plus simple est d'utiliser readline-sync

Il traite une par une entrée et sortie.

npm i readline-sync

par exemple:

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
la source
Vous devriez vraiment inclure votre requiredéclaration. Il n'y a aucune raison de le laisser de côté.
solidstatejake