NodeJS Execute a bash script that prompts for keyboard input - node.js

I am trying to execute an external bash script from nodejs.
The script when executed from terminal, prompts for input, such as a password.
When executing from nodejs, i can not get it to pause to read the input.
Can not find any good info online on this.
Is this not possible?
var shelljs = require( 'shelljs' )
var returns = shelljs.exec("passwd", {shell: '/bin/bash'}, {stdio:'inherit'});
passwd: conversation failure

shelljs.exec("exec </dev/tty\n passwd", ... )
seems to work. found by accident.
Not sure if this is the best way though.
Not very solid i think.

Related

Node.JS reading data from console command

I remember using something before in node.js that would allow me to run a command like
node appname.js text goes here
and then read the "text goes here" part with something like
console.log(console.text)
I can't remember what it is, and can't find it in any searches. Was this a real thing, or just me dreaming?
You can use process.argv to console the input from command line.
If you run below command in terminal/command line:
node appname.js text goes here.
You can print the command line arguments by:
console.log(process.argv)
Output of above console will be:
['node',
'/home/user/path/to/appname.js',
'text',
'goes',
'here' ]
If you dont want first two text, you can use:
console.log(process.argv.slice(2))
Output of above console will be:
['text',
'goes',
'here' ]
Read this link for more info.
Hope this helps you out!!!
Well there is lot's ways/packages around for reading from arguments.
the nodejs process is the base of it so check here
And also as i said lot's of packages there for parsing arguments.
yargs is one of them, minimist is also a populer one as far as i know.
If you don't want t use a package basicly it starts like this:
// inside node file
const args = process.argv.splice(2);
console.log(args);
// we are splice'ing from 2 cause
// process.argv[0] is your node-path
// process.argv[1] is your js file's full path
// Most of the time we are not using those so :)
So hope these would work for you ☺

How to get mongo shell output(three dot) for unterminated command

When type a unterminated command in a mongo shell, it will return three dots indicating need more input to complete this command like below:
> db.test.find(
... {
...
I am using nodejs child_process.spawn to create a mongo shell process and listen on its output. I can get the standard and error output from the mongo shell but I can't get the ... output. Below is my nodejs code:
const shell = spawn('mongo', params);
shell
.stdout
.on('data', (data) => {
winston.debug('get output ' + data);
});
shell
.stderr
.on('data', (data) => {
const output = data + '';
winston.error('get error output ', data);
});
I run below code to send command on the shell:
shell.stdin.write('db.test.find(');
I wander why I can't get the ... output on above method. Is it a special output?
EDIT1
I tried to use node-pty and pty.js. They can get the ... output but they mix the input and output data together. It is not possible to separate them.
I also tried to use stdbuf and unbuffer to disable buffer but it still doesn't work.
It seems that nodejs child_process doesn't work well with interactive command.
Your code doesn't include anything that writes to the stdin of your child process so I would be surprised if you got the ellipsis that indicates incomplete command when in fact you don't send any command at all - incomplete or otherwise.
That having been said, many command line utilities behave differently when they discover a real terminal connected to their stdin/stdout. E.g. git log will page the results when you run it directly but not when you pipe the results to some other command like git log | cat so this may also be the case here.
This can also have to do with the buffering - if your stream is line-buffered then you won't see any line that is not ended with a newline right away.
The real question is: do you see the > prompt? Do you send any command to the mongo shell?
Scritping interactive CLI tools can be tricky. E.g. see what I had to do to test a very simple interactive program here:
https://github.com/rsp/rsp-pjc-c01/blob/master/test-z05.sh#L8-L16
I had to create two named pipes, make sure that stdin, stderr and stdout are not buffered, and then use some other tricks to make it work. It is a shell script but it's just to show you an example.

cannot create /dev/stdout: No such device or address

I'm want to run a shell command via node and capture the result of stdout. My script works fine on OSX, but not on Ubuntu.
I've simplified the problem and script to the following node script:
var execSync = require('child_process').execSync,
result = execSync('echo "hello world" >> /dev/stdout');
// Do something with result
Results in:
/bin/sh: 1: cannot create /dev/stdout: No such device or address
I have tried replacing /dev/stdout with /dev/fd/1
I have tried changing the shell to bash... execSync('echo ...', {shell : '/bin/bash'})
Like I said, the problem above is simplified. The real script accepts as a parameter the name of a file where results should be written, so I need to resolve this by providing access to the stdout stream as a file descriptor, i.e. /dev/stdout.
How can I execute a command via node, while giving the command access to its own stdout stream?
On /dev/stdout
I don't have access to an OSX box, but from this issue on phantomjs, it seems that while on both OSX/BSD and Linux /dev/stdout is a symlink, nonetheless it seems to work differently between them. One of the commenters said it's standard on OSX to use /dev/stdout but not for Linux. In another random place I read statements that imply /dev/stdout is pretty much an OSX thing. There might be a clue in this answer as to why it doesn't work on Linux (seems to implicitly close the file descriptor when used this way).
Further related questions:
https://unix.stackexchange.com/questions/36403/portability-of-dev-stdout
bash redirect to /dev/stdout: Not a directory
The solution
I tried your code on Arch and it indeed gives me the same error, as do the variations mentioned - so this is not related to Ubuntu.
I found a blog post that describes how you can pass a file descriptor to execSync. Putting that together with what I got from here and here, I wrote this modified version of your code:
var fs = require('fs');
var path = require('path');
var fdout = fs.openSync(path.join(process.cwd(), 'stdout.txt'), 'a');
var fderr = fs.openSync(path.join(process.cwd(), 'stderr.txt'), 'a');
var execSync = require('child_process').execSync,
result = execSync('echo "hello world"', {stdio: [0,fdout,fderr] });
Unless I misunderstood your question, you want to be able to change where the output of the command in execSync goes. With this you can, using a file descriptor. You can still pass 1 and 2 if you want the called program to output to stdout and stderr as inherited by its parent, which you've already mentioned in the comments.
For future reference, this worked on Arch with kernel version 4.10.9-1-ARCH, on bash 4.4.12 and node v7.7.3.

How do I escape command injection in node child_process?

I'm adding arguments to child_process.exec command using string concatenation and they are ignored
var exec = require( "child_process" ).exec;
var cmd = exec( "grunt build --project="+application, {
cwd: application
},
function( error, stdout, stderr ){});
cmd.stdout.pipe( process.stdout );
cmd.stderr.pipe( process.stderr );
Why is string concatenation a problem and how to avoid it?
Your code is vulnerable to command injection. It depends where's application coming from and you need to make sure it's not customizable by user.
Malicious code in your example would be
var application = '; rm -rf .'
but it wouldn't work since you're also trying to change the current directory via pwd.
The general recommendation is to be careful with child_process.exec and use child_process.execFile or child_process.spawn instead.
Check your grunt build task to see if there is anything wrong. There is nothing wrong in your code with string concatenation in child_process.exec

How to write a bash script to give another program response

I have a bash script that does several tasks, including python manage.py syncdb on a fresh database. This command asks for input, like the login info for the admin. Currently, I just type this into the command line every time. Is there a way I can automatically provide these replies as part of the bash script?
Thanks, I don't really know anything about bash.
I'm using Ubuntu 10.10.
I answered a similar question on SF, but this one is more general, and it's good to have on SO.
"You want to use expect for this. It's probably already on your machine [try which expect]. It's the standard tool for any kind of interactive command-line automation. It's a Tcl library, so you'll get some Tcl skills along the way for free. Beware; it's addictive."
I should mention in this case that there is also pexpect, which is a Python expect-alike.
#!/path/to/expect
spawn python manage.py syncdb
expect "login:*"
send -- "myuser\r"
expect "*ssword:*"
send -- "mypass\r"
interact
If the program in question cannot read the input from stdin such as:
echo "some input" | your_progam
then you'll need to look to something like expect and/or autoexepect
You can give defaults values to the variables. In line 4 and 5, if the variables RSRC and LOCAL aren't set, they are set to those default values. This way you can give the options to your script or use the default ones
#!/bin/bash
RSRC=$1
LOCAL=$2
: ${RSRC:="/var/www"}
: ${LOCAL:="/disk2/backup/remote/hot"}
rsync -avz -e 'ssh ' user#myserver:$RSRC $LOCAL
You can do it like this, given an example login.py script:
if __name__ == '__main__':
import sys
user = sys.stdin.readline().strip()
passwd = sys.stdin.readline().strip()
if user == 'root' and passwd == 'password':
print 'Login successful'
sys.exit(0)
sys.stderr.write('error: invalid username or password\n')
sys.exit(1)
good-credentials.txt
root
password
bad-credentials.txt
user
foo
Then you can do the login automatically using:
$cat good-credentials.txt | python login.py
Login successful
$cat bad-credentials.txt | python login.py
error: invalid username or password
The down-side of this approach is you're storing your password in plain text, which isn't great practice.

Resources