shelljs icon indicating copy to clipboard operation
shelljs copied to clipboard

processes started via `exec` are unable to accept input

Open despairblue opened this issue 10 years ago • 13 comments

If I try to launch npm init from a shelljs script it seems that the stdin is not passed through to that process:

require('shelljs/global')

exec('npm init')

=>

exec npm init { stdio: 'inherit' }
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (drive) foobar









Pressing enter has no effect.

despairblue avatar Apr 12 '16 14:04 despairblue

Node version?

nfischer avatar Apr 12 '16 16:04 nfischer

v5.8.0

despairblue avatar Apr 13 '16 15:04 despairblue

Confirmed as a bug.

As a workaround, please use child_process.execSync('npm init', {stdio: 'inherit'});

Fixes are welcomed.

nfischer avatar Apr 14 '16 03:04 nfischer

There are several ways to fix this issue. In the exec.js source file, you can either :

  • Pipe process.stdin to childProcess.stdin process.stdin.pipe(childProcess.stdin);
  • Use spawn instead of exec in your string scriptFile
  • Execute the command and not the scriptFile in execSync

The main problem is that i can't understand why you have to build a script in a script. execSync can run a command.

script = [
      "var child = require('child_process')",
      "  , fs = require('fs');",
      "var childProcess = child.exec('"+escape(cmd)+"', {env: process.env, maxBuffer: "+opts.maxBuffer+"}, function(err) {",
      "  fs.writeFileSync('"+escape(codeFile)+"', err ? err.code.toString() : '0');",
      "});",
      "var stdoutStream = fs.createWriteStream('"+escape(stdoutFile)+"');",
      "var stderrStream = fs.createWriteStream('"+escape(stderrFile)+"');",
      "childProcess.stdout.pipe(stdoutStream, {end: false});",
      "childProcess.stderr.pipe(stderrStream, {end: false});",
      "childProcess.stdout.pipe(process.stdout);",
      "childProcess.stderr.pipe(process.stderr);",
      "var stdoutEnded = false, stderrEnded = false;",
      "function tryClosingStdout(){ if(stdoutEnded){ stdoutStream.end(); } }",
      "function tryClosingStderr(){ if(stderrEnded){ stderrStream.end(); } }",
      "childProcess.stdout.on('end', function(){ stdoutEnded = true; tryClosingStdout(); });",
      "childProcess.stderr.on('end', function(){ stderrEnded = true; tryClosingStderr(); });"
    ].join('\n');

MatthieuLemoine avatar Apr 25 '16 21:04 MatthieuLemoine

@MatthieuLemoine

The main problem is that i can't understand why you have to build a script in a script. execSync can run a command.

This is a pretty ancient part of the code base that's stuck around because it allows us to send command output to the terminal in real-time while still capturing it for when the function returns. Even with backwards compatibility in mind, there's probably a much smarter way to revise this function. Pull requests, as always, are welcome :smile:

One solution that comes to mind would be, as you suggested, to use spawn(). The main drawback is that it doesn't support glob characters (as far as I'm aware). This would be nice, however, as an option for situations where globbing isn't necessary (it would fix some of the security concerns surrounding globbing, too!).

nfischer avatar Apr 25 '16 22:04 nfischer

I'm going to try to re-write exec using some of my work from #402.

ariporad avatar Apr 26 '16 00:04 ariporad

@ariporad So is there any reason execAsync does the following instead of just using the {stdio: 'inherit'} option?

  c.stdout.on('data', function (data) {
    stdout += data;
    if (!opts.silent) process.stdout.write(data);
  });

  c.stderr.on('data', function (data) {
    stderr += data;
    if (!opts.silent) process.stderr.write(data);
  });

jedwards1211 avatar Sep 06 '16 15:09 jedwards1211

@jedwards1211: Yes... There is no other way to capture stdout from the child.

ariporad avatar Sep 07 '16 02:09 ariporad

Oh right. I've noticed that for some reason transient output like npm's progress bar works when I use inherit but not when I do something like the above (or better, do c.stdout.pipe(process.stdout)). I haven't figured out why that is. But it might be nice to have an option to use inherit and not capture output for that purpose.

jedwards1211 avatar Sep 07 '16 15:09 jedwards1211

I added the workaround to the FAQs: https://github.com/shelljs/shelljs/wiki/FAQ#running-interactive-programs-with-exec

nfischer avatar Oct 18 '17 02:10 nfischer

Has anyone found a fix for this because it is still a problem in shelljs v0.8.3

RastogiAbhijeet avatar Mar 30 '20 01:03 RastogiAbhijeet

@RastogiAbhijeet https://github.com/shelljs/shelljs/wiki/FAQ#running-interactive-programs-with-exec

nfischer avatar Mar 30 '20 04:03 nfischer

Is there any chance I can help in fixing this issue ?

RastogiAbhijeet avatar Mar 30 '20 04:03 RastogiAbhijeet