I have a list of shell commands I want to execute with nodejs:
// index.js
var commands = ["npm install", "echo 'hello'"];
var exec = require('child_process').exec;
for (var i = 0; i < commands.length; i++) {
exec(commands[i], function(err, stdout) {
console.log(stdout);
});
}
When I run this, the commands are executed in the reverse order. Why is this happening? How do i execute the commands in sequence?
Better yet, is there a way to execute shell commands without using nodejs? I find its async handling of the shell a little cumbersome.
NOTE:
I know that libraries like shelljs exist. I'm trying to do this with base nodejs only.
Your for loop is executing all your asynchronous operation in parallel at once because exec() is non-blocking. The order they will complete depends upon their execution time and will not be determinate. If you truly want them to be sequenced, then you have to execute one, wait for it to call it's completion callback and then execute the next one.
You can't use a traditional for loop to "wait" on an asynchronous operation to complete in Javascript in order to execute them sequentially. Instead, you have to make the iteration manually where you kick off the next iteration in the completion callback of the previous one. My usual way of doing that is with a counter and a local function called next() like this:
Manual Async Iteration
var commands = ["npm install", "echo 'hello'"];
var exec = require('child_process').exec;
function runCommands(array, callback) {
var index = 0;
var results = [];
function next() {
if (index < array.length) {
exec(array[index++], function(err, stdout) {
if (err) return callback(err);
// do the next iteration
results.push(stdout);
next();
});
} else {
// all done here
callback(null, results);
}
}
// start the first iteration
next();
}
runCommands(commands, function(err, results) {
// error or results here
});
ES6 Promises
Since promises have been standardized in ES6 and are built into node.js now, I like to use Promises for my async operations:
var exec = require('child_process').exec;
function execPromise = function(cmd) {
return new Promise(function(resolve, reject) {
exec(cmd, function(err, stdout) {
if (err) return reject(err);
resolve(stdout);
});
});
}
var commands = ["npm install", "echo 'hello'"];
commands.reduce(function(p, cmd) {
return p.then(function(results) {
return execPromise(cmd).then(function(stdout) {
results.push(stdout);
return results;
});
});
}, Promise.resolve([])).then(function(results) {
// all done here, all results in the results array
}, function(err) {
// error here
});
Bluebird Promises
Using the Bluebird promise library, this would be even simpler:
var Promise = require('bluebird');
var execP = Promise.promisify(require('child_process').exec);
var commands = ["npm install", "echo 'hello'"];
Promise.mapSeries(commands, execP).then(function(results) {
// all results here
}, function(err) {
// error here
});
Opt.1: Use the '...Sync' version of the function if it exists
In this case there is already an execSync function:
child_process.execSync(command[, options])
Opt.2: Generators magic!
For a more general purpose, nowadays you could use e.g. this 'generator' pattern to 'deasync' any async function inside them, very useful for any sequential OS script.
Here an example of how to use readline async function in a sync fashion in node.js v6+ (I think also v4+)
var main = (function* () {
var rl = require('readline')
.createInterface({input: process.stdin, output: process.stdout });
// the callback uses the iterator '.next()' to resume the 'yield'
a = yield rl.question('do you want this? ', r=>main.next(r))
b = yield rl.question('are you sure? ', r=>main.next(r))
rl.close()
console.log(a,b)
})() // <- generator executed, iterator 'main' created
main.next() // <- start iterator, run till the first 'yield'
Related
I have not been able to test this function with Jest, I would appreciate your help. Thank you.
/**
* #function now
* #description When a Bash script is executed, it instantly displays the responses that appear on the screen.
* #param {string} script Bash script
* #example now('echo "Hello World!"')
*/
function now(script) {
let execute = exec(script)
execute.stdout.on('data', (data) => {
if (data.charAt(data.length - 1) === '\n') {
console.log(data.toString().slice(0, -1))
} else {
console.log(data.toString())
}
})
}
Since exec executes asynchronously, you'll need to change the now function a bit in order to test it. As it's written, you don't have a way for the caller to know when now is finished. The simplest ways to change this are:
Make it return a Promise that resolves when it's done
Make it take a callback that is called when it's done
Promise
function now(script) {
return new Promise((resolve, reject) => {
let execute = exec(script)
execute.stdout.on('data', (data) => {
if (data.charAt(data.length - 1) === '\n') {
console.log(data.toString().slice(0, -1))
} else {
console.log(data.toString())
}
})
// Resolve the Promise when the shell exits
execute.on('exit', resolve);
// Error handling omitted for brevity, you can call reject() on failure
});
}
Here's the test. There are various ways to check that console.log is called. This one uses a spy but you could also inject a log function and default it to console.log.
it('logs from shell command', async () => {
jest.spyOn(console, "log");
await greet('echo "foo"');
expect(console.log).toBeCalledWith('foo');
});
Callback
function now(script, callback) {
let execute = exec(script)
execute.stdout.on('data', (data) => {
if (data.charAt(data.length - 1) === '\n') {
console.log(data.toString().slice(0, -1))
} else {
console.log(data.toString())
}
})
// Resolve the Promise when the shell exits
execute.on('exit', callback);
// Error handling omitted for brevity, you can pass an error to the callback on failure
}
The expect needs to be in the callback so the test waits until the shell has exited. Note that you need to call done for the test to finish. Promises are generally more ergonomic, but callbacks have their uses as well so I included them both.
it('logs from shell command', (done) => {
jest.spyOn(console, "log");
greet('echo "foo"', () => {
expect(console.log).toBeCalledWith('foo');
done()
});
});
Using information from #helloitsjoe I got what I was looking for.
Modify the promise so that it correctly displays the messages on the screen.
function now(script) {
return new Promise((resolve, reject) => {
let execute = exec(script)
execute.stdout.on('data', (data) => {
console.log(data.toString().slice(0, -1))
process.stdout.cursorTo(0)
})
execute.on('exit', resolve);
})
}
I use the test with the promise of #helloitsjoe, for it to work I need to install npm i -D regenerator-runtime and import it with import 'regenerator-runtime/runtime'.
Thank you very much #helloitsjoe.
Using child process I execute a Python script does something a spits data back. I used a Node promise to wait until I get the Python data.
The problem I am facing is there is a callback for an anonymous function, the callback takes two parameters one of which is the python data. Code below explains. How do I call the promise, wait until it resolves then call the callback.
Node Promise
var spawn = require("child_process").spawn;
function sensorData()
{
return new Promise(function(resolve, reject)
{
var pythonProcess = spawn ("python",[pythonV1.py"]);
pythonProcess.stdout.on("data", function(data)
{
resolve(data);
});
});
}
Anonymous Function
...
onReadRequest : function(offest, callback)
{
#============DOES NOT WORK=========================
sensorData()
.then(function(data)
{
callback(this.RESULT_SUCCESS, data);
})
#===================================================
#call promise, wait and then call callback passing the python data
callback(this.RESULT_SUCCESS, new Buffer(#python data)
}
...
Many thanks
Unless you know that your pythonProcess will only return one line of data, it's bad practice to call resolve() on every stdout data call. It would be much better to collect data until the process closes, and return it all at once.
I'm also not used to dealing with buffers, so I'm casting stuff to strings here...
var spawn = require("child_process").spawn;
function sensorData()
{
return new Promise(function(resolve, reject)
{
var output = '';
var pythonProcess = spawn ("python",[pythonV1.py"]);
pythonProcess.stdout.on("data", function(data)
{
output += data.toString();
});
// Not sure if all of these are necessary
pythonProcess.on('disconnect', function()
{
resolve(output);
});
pythonProcess.on('close', function(code, signal)
{
resolve(output);
});
pythonProcess.on('exit', function(code, signal)
{
resolve(output);
});
});
}
...
onReadRequest : function(offest, callback)
{
#call promise, wait and then call callback passing the python data
sensorData()
.then(function(data)
{
callback(this.RESULT_SUCCESS, data);
})
.catch(function(err)
{
// Do something, presumably like:
callback(this.RESULT_FAILURE, err);
});
}
...
I am using Q library and would like to make the promise2 function to wait until the promise1 function has finished execution.
In the following example the promise2 function gets executed before the promise1 function finishes execution.
What am I doing wrong here?
var Q = require("q");
var fs = require('fs');
function promise1() {
var deferred = new Q.defer();
fs.readFile('hostname.json', function (err, data) {
if (err){
return console.error(err)
}else {
console.log('file read');
return deferred.resolve(JSON.parse(data));
}
});
return deferred.promise;
}
function promise2(){
var deferred = new Q.defer();
var path = 2;
console.log("2");
return deferred.resolve(path);
}
Q(promise1())
.then(promise2());
here is the working example, might give errors since the readfile does not exist, but it does exists in my dev environment.
I get the following result when I run:
>2
>file read
Result I want:
>file read
>2
When you write promise2() you execute it immediately. Try:
promise1().then(promise2);
In a node.js, I'd like to find a way to obtain the output of a Unix terminal command. Is there any way to do this?
function getCommandOutput(commandString){
// now how can I implement this function?
// getCommandOutput("ls") should print the terminal output of the shell command "ls"
}
This is the method I'm using in a project I am currently working on.
var exec = require('child_process').exec;
function execute(command, callback){
exec(command, function(error, stdout, stderr){ callback(stdout); });
};
Example of retrieving a git user:
module.exports.getGitUser = function(callback){
execute("git config --global user.name", function(name){
execute("git config --global user.email", function(email){
callback({ name: name.replace("\n", ""), email: email.replace("\n", "") });
});
});
};
If you're using node later than 7.6 and you don't like the callback style, you can also use node-util's promisify function with async / await to get shell commands which read cleanly. Here's an example of the accepted answer, using this technique:
const { promisify } = require('util');
const exec = promisify(require('child_process').exec)
module.exports.getGitUser = async function getGitUser () {
// Exec output contains both stderr and stdout outputs
const nameOutput = await exec('git config --global user.name')
const emailOutput = await exec('git config --global user.email')
return {
name: nameOutput.stdout.trim(),
email: emailOutput.stdout.trim()
}
};
This also has the added benefit of returning a rejected promise on failed commands, which can be handled with try / catch inside the async code.
You're looking for child_process
var exec = require('child_process').exec;
var child;
child = exec(command,
function (error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
As pointed out by Renato, there are some synchronous exec packages out there now too, see sync-exec that might be more what yo're looking for. Keep in mind though, node.js is designed to be a single threaded high performance network server, so if that's what you're looking to use it for, stay away from sync-exec kinda stuff unless you're only using it during startup or something.
Requirements
This will require Node.js 7 or later with a support for Promises and Async/Await.
Solution
Create a wrapper function that leverage promises to control the behavior of the child_process.exec command.
Explanation
Using promises and an asynchronous function, you can mimic the behavior of a shell returning the output, without falling into a callback hell and with a pretty neat API. Using the await keyword, you can create a script that reads easily, while still be able to get the work of child_process.exec done.
Code sample
const childProcess = require("child_process");
/**
* #param {string} command A shell command to execute
* #return {Promise<string>} A promise that resolve to the output of the shell command, or an error
* #example const output = await execute("ls -alh");
*/
function execute(command) {
/**
* #param {Function} resolve A function that resolves the promise
* #param {Function} reject A function that fails the promise
* #see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
*/
return new Promise(function(resolve, reject) {
/**
* #param {Error} error An error triggered during the execution of the childProcess.exec command
* #param {string|Buffer} standardOutput The result of the shell command execution
* #param {string|Buffer} standardError The error resulting of the shell command execution
* #see https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
*/
childProcess.exec(command, function(error, standardOutput, standardError) {
if (error) {
reject();
return;
}
if (standardError) {
reject(standardError);
return;
}
resolve(standardOutput);
});
});
}
Usage
async function main() {
try {
const passwdContent = await execute("cat /etc/passwd");
console.log(passwdContent);
} catch (error) {
console.error(error.toString());
}
try {
const shadowContent = await execute("cat /etc/shadow");
console.log(shadowContent);
} catch (error) {
console.error(error.toString());
}
}
main();
Sample Output
root:x:0:0::/root:/bin/bash
[output trimmed, bottom line it succeeded]
Error: Command failed: cat /etc/shadow
cat: /etc/shadow: Permission denied
Try it online.
Repl.it.
External resources
Promises.
child_process.exec.
Node.js support table.
Thanks to Renato answer, I have created a really basic example:
const exec = require('child_process').exec
exec('git config --global user.name', (err, stdout, stderr) => console.log(stdout))
It will just print your global git username :)
You can use the util library that comes with nodejs to get a promise from the exec command and can use that output as you need. Use destructuring to store the stdout and stderr in variables.
const util = require('util');
const exec = util.promisify(require('child_process').exec);
async function lsExample() {
const {
stdout,
stderr
} = await exec('ls');
console.log('stdout:', stdout);
console.error('stderr:', stderr);
}
lsExample();
you can use ShellJS package.
ShellJS is a portable (Windows/Linux/OS X) implementation of Unix shell commands on top of the Node.js API.
see: https://www.npmjs.com/package/shelljs#execcommand--options--callback
import * as shell from "shelljs";
//usage:
//exec(command [, options] [, callback])
//example:
const version = shell.exec("node --version", {async: false}).stdout;
console.log("nodejs version", version);
Here's an async await TypeScript implementation of the accepted answer:
const execute = async (command: string): Promise<any> => {
return new Promise((resolve, reject) => {
const exec = require("child_process").exec;
exec(
command,
function (
error: Error,
stdout: string | Buffer,
stderr: string | Buffer
) {
if (error) {
reject(error);
return;
}
if (stderr) {
reject(stderr);
return;
} else {
resolve(stdout);
}
}
);
});
};
I have code like
common.findOne('list', {'listId': parseInt(request.params. istId)}, function(err, result){
if(err) {
console.log(err);
}
else {
var tArr = new Array();
if(result.tasks) {
var tasks = result.tasks;
for(var i in tasks) {
console.log(tasks[i]);
common.findOne('tasks', {'taskId':parseInt(tasks[i])}, function(err,res){
tArr[i] = res;
console.log(res);
});
}
console.log(tArr);
}
return response.send(result);
}
});
It is not executed sequentially in node.js so I get an empty array at the end of execution. Problem is it will first execute console.log(tArr); and then execute
common.findOne('tasks',{'taskId':parseInt(tasks[i])},function(err,res){
tArr[i] = res;
console.log(res);
});
Is there any mistake in my code or any other way for doing this.
Thanks!
As you are probably aware, things run asynchronously in node.js. So when you need to get things to run in a certain order you need to make use of a control library or basically implement it yourself.
I highly suggest you take a look at async, as it will easily allow you to do something like this:
var async = require('async');
// ..
if(result.tasks) {
async.forEach(result.tasks, processEachTask, afterAllTasks);
function processEachTask(task, callback) {
console.log(task);
common.findOne('tasks', {'taskId':parseInt(task)}, function(err,res) {
tArr.push(res); // NOTE: Assuming order does not matter here
console.log(res);
callback(err);
});
}
function afterAllTasks(err) {
console.log(tArr);
}
}
The main things to see here is that processEachTask gets called with each task, in parallel, so the order is not guaranteed. To mark that the task has been processed, you will call callback in the anonymous function from findOne. This allows you to do more async work in processEachTask but still manage to signify when it is done. When every task is done, it will then call afterAllTasks.
Take a look at async to see all the helper functions that it provides, it is very useful!
I've recently created a simple abstraction named "wait.for" to call async functions in sync mode (based on Fibers): https://github.com/luciotato/waitfor
Using wait.for and async your code will be:
var wait = require('waitfor');
...
//execute in a fiber
function handleRequest(request,response){
try{
...
var result = wait.for(common.findOne,'list',{'listId': parseInt(request.params.istId)});
var tArr = new Array();
if(result.tasks) {
var tasks = result.tasks;
for(var i in tasks){
console.log(tasks[i]);
var res=wait.for(common.findOne,'tasks',{'taskId':parseInt(tasks[i])});
tArr[i] = res;
console.log(res);
}
console.log(tArr);
return response.send(result);
};
....
}
catch(err){
// handle errors
return response.end(err.message);
}
};
// express framework
app.get('/posts', function(req, res) {
// handle request in a Fiber, keep node spinning
wait.launchFiber(handleRequest,req,res);
});