child_process.exec() stdout is empty string when command contains spaces - node.js

I have put the below code in a Javascript file and ran it using node.
var exec = require('child_process').exec;
var child;
child = exec("git for-each-ref --format='%(committerdate), %(authorname)' --sort=committerdate --merged develop",
function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('typeof stdout: ' + typeof stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
All I get is an empty string as stdout. There are no errors.
stdout:
stderr:
However, if I remove the space after comma in "%(committerdate), %(authorname)", I get the expected output. The below code works:-
var exec = require('child_process').exec;
var child;
child = exec("git for-each-ref --format='%(committerdate),%(authorname)' --sort=committerdate --merged develop",
function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
Why do I not get any output when there is a space after comma?
When I run the command directly on in PowerShell or git-bash, I get the same output with and without the space.
git for-each-ref --format='%(committerdate), %(authorname)' --sort=committerdate --merged develop
I spent quite some time to get the command working and it seemed to be that a space would cause an issue, especially when the command works fine when run directly.
Environment:
Windows 10
Node 16.7.0

It's probably worth putting into an answer to give people ideas on how to troubleshoot.
Let's look at the command first:
exec("git for-each-ref --format='%(committerdate), %(authorname)' --sort=committerdate --merged develop", ...);
If we wanted to look at the underbelly of the exec function, we would probably find a more sophisticated version of inputString.split(" ") so that node can distinguish the executable from the positional arguments/flags. So a good place to start is to try and escape the space in various ways to see if that works.
Next you need to understand how node executes the command. On different operating systems, the node child_process library is just a proxy to an underlying shell. So you need to make sure your command works in the target shell (which is likely not your favorite shell or the one you use on a day-to-day basis). For Windows, node likely uses cmd.exe under the hood.
Lastly, the child_process library also allows you to spawn a process which is a little more deliberate in specifying the executable and positional arguments/flags. I recommend using this method for most child_process operations:
spawn(<executable>, [...<args>]);
This way you are telling node exactly what binary to use while clearly identifying every positional argument. For example, the following will fail:
spawn('git', ['checkout -b my-new-branch']);
The reason this fails is because node treats the string 'checkout -b my-new-branch' as a single argument to the git command... which is not correct. Instead, you want to clearly define each positional argument separately:
spawn('git', ['checkout', '-b', 'my-new-branch']);
This is particularly useful if one of your arguments contains spaces or other special shell characters:
spawn('git', ['checkout', '--format=\'%(committerdate), %(authorname)\'', '--sort=committerdate', '--merged develop']);
Changing the underlying shell
For any of the child_process creation methods, you can pass an option for which shell to use. Be careful using this option because you can destroy the ability for your code to run on multiple operating systems. Make sure to read about shell requirements and the default windows shell:
exec("...", { shell: "powershell.exe" });
spawn("...", [...], { shell: "powershell.exe" });

Related

Why do nodejs exec/spawn not show any output for bash 'history' command?

I am running this on Ubuntu and have tried many variations of exec/spawn functions (and their sync counterparts) and none of them can show me an output for bash 'history' command. One scenario is following:
const { spawnSync} = require('child_process');
const child = spawnSync('history', { shell: "/bin/bash" });
console.log('error: ', child.error);
console.log('stdout: ', child.stdout.toString());
console.log('stderr: ', child.stderr);
It does not show any errors and output is empty. I think this question has more to do with 'specialty' or category of the history command than nodejs's function since they work fine for normal commands like ls, pwd, whoami, etc work fine. I have looked at my .bash_history file and its filled with history so that's not the issue.
Another problem that might be similar is ll command also fails even though I have set bash as shell. But for ll, it does return an error:
/bin/bash: ll: command not found
Just to be sure, I tried running ll command in bash it worked just fine. What am I missing here?
edit: I have done some more testing it seems more like a bash thing than a node thing. When I simply write the history command, bash prints results but when I do bash -c history, it does not show any output but also no error.
.
You need to subscribe to messages from child process
child.on('error', (err) => {
});
child.stderr.on('data', (data) => {
});
child.on('exit', (code, signal) => {
});

How to run shell script file using nodejs?

I need to run a shell script file using nodeJS that executes a set of Cassandra DB commands. Can anybody please help me on this.
inside db.sh file:
create keyspace dummy with replication = {'class':'SimpleStrategy','replication_factor':3}
create table dummy (userhandle text, email text primary key , name text,profilepic)
You could use "child process" module of nodejs to execute any shell commands or scripts with in nodejs. Let me show you with an example, I am running a shell script(hi.sh) with in nodejs.
hi.sh
echo "Hi There!"
node_program.js
const { exec } = require('child_process');
var yourscript = exec('sh hi.sh',
(error, stdout, stderr) => {
console.log(stdout);
console.log(stderr);
if (error !== null) {
console.log(`exec error: ${error}`);
}
});
Here, when I run the nodejs file, it will execute the shell file and the output would be:
Run
node node_program.js
output
Hi There!
You can execute any script just by mentioning the shell command or shell script in exec callback.
You can execute any shell command using the shelljs module
const shell = require('shelljs')
shell.exec('./path_to_your_file')
you can go:
var cp = require('child_process');
and then:
cp.exec('./myScript.sh', function(err, stdout, stderr) {
// handle err, stdout, stderr
});
to run a command in your $SHELL.
Or go
cp.spawn('./myScript.sh', [args], function(err, stdout, stderr) {
// handle err, stdout, stderr
});
to run a file WITHOUT a shell.
Or go
cp.execFile();
which is the same as cp.exec() but doesn't look in the $PATH.
You can also go
cp.fork('myJS.js', function(err, stdout, stderr) {
// handle err, stdout, stderr
});
to run a javascript file with node.js, but in a child process (for big programs).
EDIT
You might also have to access stdin and stdout with event listeners. e.g.:
var child = cp.spawn('./myScript.sh', [args]);
child.stdout.on('data', function(data) {
// handle stdout as `data`
});
Also, you can use shelljs plugin.
It's easy and it's cross-platform.
Install command:
npm install [-g] shelljs
What is shellJS
ShellJS is a portable (Windows/Linux/OS X) implementation of Unix
shell commands on top of the Node.js API. You can use it to eliminate
your shell script's dependency on Unix while still keeping its
familiar and powerful commands. You can also install it globally so
you can run it from outside Node projects - say goodbye to those
gnarly Bash scripts!
An example of how it works:
var shell = require('shelljs');
if (!shell.which('git')) {
shell.echo('Sorry, this script requires git');
shell.exit(1);
}
// Copy files to release dir
shell.rm('-rf', 'out/Release');
shell.cp('-R', 'stuff/', 'out/Release');
// Replace macros in each .js file
shell.cd('lib');
shell.ls('*.js').forEach(function (file) {
shell.sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
shell.sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', file);
shell.sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, shell.cat('macro.js'), file);
});
shell.cd('..');
// Run external tool synchronously
if (shell.exec('git commit -am "Auto-commit"').code !== 0) {
shell.echo('Error: Git commit failed');
shell.exit(1);
}
Also, you can use from the command line:
$ shx mkdir -p foo
$ shx touch foo/bar.txt
$ shx rm -rf foo

Cannot get output of child_process.spawn with interactive scripts

I cannot get any output in the following code:
var spawn = require('child_process').spawn,
script = 'ftp',
child = spawn(script);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('close', function (code) {
console.log('child process exited with code ' + code);
});
It works for normal scripts such as 'ls', 'pwd' etc. But not for interactive programs such as 'ftp', 'telnet'. Any suggestions?
Edit:
Take another script for example:
#!/usr/bin/env python
name = raw_input("your name>")
print name
When spawn this script, I wish to fetch the prompt "your name>" with the data event, so that I can latter input something into stdin.
The problem is that I got nothing in the data event, and it seemed that none of these events are triggered.
ls, cat is controllable via input output and error stream.
ftp, telnet is controllable indirectly via tty.
The protocol is also base on input/output stream but it is more complicated. You can use available package to handle that protocol.
https://github.com/chjj/pty.js
var pty = require('pty.js');
var term = pty.spawn('ftp', [], options);
term.on('data', function(data) {
console.log(data);
});
term.write(ftpCmd + '\r');
The author of pty have some interesting examples, he forward pty to web via web socket, including terminal games:
https://github.com/chjj/tty.js
In interactive mode there is a command interpreter that reads user input from stdin, then accodingly prints output. So you have to write to stdin to do something. For example add following lines to your code with telnet command:
child.stdin.write('?\n');
child.stdin.write('quit\n');
Output:
stdout: Commands may be abbreviated. Commands are:
! cr mdir proxy send
$ delete mget sendport site
account debug mkdir put size
append dir mls pwd status
ascii disconnect mode quit struct
bell form modtime quote system
binary get mput recv sunique
bye glob newer reget tenex
case hash nmap rstatus trace
ccc help nlist rhelp type
cd idle ntrans rename user
cdup image open reset umask
chmod lcd passive restart verbose
clear ls private rmdir ?
close macdef prompt runique
cprotect mdelete protect safe
child process exited with code 0

Can Node.js launch scripts on the server?

Can Node.js launch scripts on the server it is installed on? Scripts like bash scripts or PHP scripts, for example to resize pictures?
If so, how how is it? Can you point me to a documentation page, please?
Thanks,
Dan
Yes it is possible. The following give a demonstration:
http://www.dzone.com/snippets/execute-unix-command-nodejs
http://groups.google.com/group/nodejs/browse_thread/thread/e3d00bb0e48dd760?pli=1
You can also perform tasks such as spawning child processes, and clustering.
Executing a unix command:
var exec = require('child_process').exec;
child = exec("something", function (error, stdout, stderr) {
sys.print('stdout: ' + stdout);
sys.print('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
See Node.js Documentation for more: http://nodejs.org/api/child_process.html

node.js using spawn for perl - stdout line by line

Spawn in nodeJS. I have just about managed to use this to run a bash command as follows. This seems to be pretty much non-blocing and I get action on the browser screen as the command trashes through data.
ls = spawn('find',['/'] );
response.writeHead(200, { "Content-Type": "text/plain" });
ls.stdout.on('data', function (data) {
console.log('stdout: ' + data);
response.write(data);
});
But I want to run a perl script with multiple arguments.
ls = spawn('blah.pl',['--argstring here', '--arg blah'] );
Perl script is just written to get arguments using getopts CPAN lib and it using CPAN expect lib to run though a pile of stuff - outputs to stdout and stderr if I have an error but I mostly care about stdout right now.
The thing is this is giving me no output. Seems to be completely blocking at least until the program finishes execution ... and it this case it doesn't at least for 10 mins.
Am I using spawn wrong?
I like the node module "carrier"
carrier = require "carrier"
childproc = require "child_process"
find = childproc.spawn "find"
find.stdout.setEncoding "utf8"
linereader = carrier.carry find.stdout
linereader.on "line", (line) -> console.log line

Resources