How do I specify the shell executable for child_process.exec? - node.js

I'm using GitBash in windows 10 and want to execute git commands within a child_process.exec call. I think that since I installed git through "Git For Windows", I just need specify the shell as the GitBash executable. I've tried every variation of the path to GitBash executable that I could think of and it always fails. What is the path that node is looking for?
Example of paths that do not work
c:/program files/git/usr/bin/bash
c:/program\ files/git/usr/bin/bash
/c/program\ files/git/usr/bin/bash
c:\\program files\\git\\usr\\bin\\bash
const { expect } = require('chai');
const { exec } = require('child_process');
describe.only('exec', function(){
it('should work', function(done){
let shellPath = "c:\\program files\\git\\usr\\bin\\bash";
expect(exec(`cat <<< "abc"`, { shell: shellPath }, (err, stdout) => {
expect(err).to.be.null;
expect(stdout.trim()).to.be.equal("abc");
done();
}));
});
});
The first assertion fails with:
expected [Error: Command failed: cat <<< "abc" << was unexpected at this time.] to be null

There are some problems with this approach.
As the reference states, exec automatically uses Windows-specific shell arguments that won't work for Bash.
Another problem is that PATH may not be set to GitBash binaries path.
This should likely work:
delete process.platform;
process.platform = 'linux';
exec(`cat <<< "abc"`, {
env: { PATH: 'C:\\Program Files\\git\\usr\\bin' },
shell: 'C:\\Program Files\\git\\usr\\bin\\bash.exe'
}, (err, stdout) => {
...
});
process.platform = 'win32';
The workability of this solution may depend on bash.exe implementation.
The use of custom shell isn't needed to run git in Node; this is handled by Git executable.

Related

How to test an node file, receiving an 'EACCES' error when spawning the function

When creating a CLI I would like to test my function. For that I'm using the module child_process.
const path = require('path');
const { execFile } = require('child_process');
describe('cli test', () => {
test('thing', () => {
const myCli = execFile(
`${path.resolve(__dirname, '..')}/cli.js`, ['--foo', 'Bar'],
(err, stdout, stderr) => {
if (err) {
console.log('err: ', err);
}
});
});
But this produces the following error:
Attempted to log "err: { Error: spawn /projects/cli/src/cli.js EACCES
at Process.ChildProcess._handle.onexit (internal/child_process.js:240:19)
at onErrorNT (internal/child_process.js:415:16)
at process._tickCallback (internal/process/next_tick.js:63:19)
errno: 'EACCES',
code: 'EACCES',
Running this script directly in the terminal via the command: $ node cli.js --foo Bar works perfectly.
Now a suggestion is to chmod +x <file> that file (source). But the test should also work on CI, and on a different computer which pulls the Git repo.
Any idea?
I'd suggest using fork instead of execFile.
The child_process.fork() method is a special case of child_process.spawn() used specifically to spawn new Node.js processes.
This will allow you to execute JS files without needing them to be shell executable.
To the best of my knowledge, git actually tracks the executable bit for files. There are some things to consider though as pointed out in this article: https://medium.com/#tahteche/how-git-treats-changes-in-file-permissions-f71874ca239d
Another solution would be to not rely on the ./ execution syntax (which requires the executable bit to be turned on for the respective file) but instead to explicitly use the shell command:
const path = require('path');
const { execFile } = require('child_process');
describe('cli test', () => {
test('thing', () => {
const myCli = execFile(
`sh ${path.resolve(__dirname, '..')}/cli.js`, ['--foo', 'Bar'],
(err, stdout, stderr) => {
if (err) {
console.log('err: ', err);
}
});
});
Notice the sh prefix I added to your code, This way you thell the sh command (which should be available in all of your environments e.g. the CI) to execute the contents of the file, regardless of whether the file itself can be executed or not!
I was receiving an EACCESS -13 error from child_process.spawn when trying to run a the command line mysql command.
There was something wrong with my PATH and updating it to add /usr/local/mysql/bin/ resolved the problem.
The temporary fix is to run export PATH=$PATH:/usr/local/mysql/bin/.
The permanent fix is to:
type: sudo nano /etc/paths
Add /usr/local/mysql/bin at the end
Ctrl + X
Yes
Enter key
type hash -r # command line or close the terminal app and open it again
NOTE: I got the temporary fix from a site ... I don't know why it has a / on the end of the bin but all of the mysql executables appear to be available without it in the /etc/paths file

Node.js - exec shell commands with .bashrc initialized

I'm writing a small utility tool for development to sync files over ssh. Normally I use ssh-agent set up in .bashrc file to connect to my dev server easily. I'd like to use exec in the script, but calling ssh-agent, every time I make a request sounds a bit inoptimal.
Is there a way I could execute the agent code once, and then have it working for all subsequent ssh requests I make? E.g. to spawn a shell process like a terminal emulator, and then use that process to execute a command, rather than invoking a new shell with each command.
The reason I want to do this, is I don't want to store the password in a config file.
You can create one ssh process, and then execute other commands using same process. Here is an example how to use it for bash. I'm creating a new bash shell and execugte the command ls -la and exit you can execute other commands.
const cp = require("child_process")
class MyShell {
constructor(command) {
this._spawned = cp.spawn(command, {
stdio: ["pipe", "pipe", "inherit"],
})
}
execute(command, callback) {
this._spawned.stdin.write(command + "\n")
this._spawned.stdout.on("data", (chunk) => {
if (callback) {
callback(chunk.toString())
}
})
}
}
var myShell = new MyShell("bash")
myShell.execute("ls -la", (result) => {
console.log(result)
})
myShell.execute("exit")

Is there a way to launch a terminal window (or cmd on Windows) and pass/run a command?

Question
Is it possible to do the following?
open a new cmd.exe or terminal (on MacOS / Linux) window
pass / run a command, e.g. cd <path>
Problem
I can open cmd by running this command:
"$electron.shell.openItem('cmd.exe')"
But shell.openItem doesn't allow to pass the arguments / commands.
I tried using child_process but I couldn't make it work at all, it doesn't open a new terminal window:
const { spawn, exec } = require('child_process');
spawn('C:/Windows/System32/cmd.exe');
I also tried running the following command, but still nothing:
spawn( 'cmd.exe', [ '/c', 'echo ASDASD' ], { stdio: [0, 1, 2] } )
The only possible solution that I see is to create a command.bat:
start cmd.exe /K "cd /D C:\test"
And then use openItem:
"$electron.shell.openItem('command.bat')"
But that would only work on Windows
Solution
I finally found a way to do it on Windows:
var child_process = require('child_process');
child_process.exec("start cmd.exe /K cd /D C:/test");
Notes
You have to add the word start to open a new command window
Instead of cd /D C:/test you can specify any other command, e.g. python
/D is to make sure it will change the current drive automatically, depending on the path specified
/K removes the initial message
Don't use execSync it will lock the app until the terminal (command
prompt) window is closed
As for MacOS, looks like it's possible to do with osascript
osascript -e 'tell application "Terminal" to activate' -e 'tell application "System Events" to tell process "Terminal" to keystroke "t" using command down'
Here is a working example showing how to open a Terminal window at a specific path (~/Desktop for instance) on macOS, from a renderer script:
const { app } = require ('electron').remote;
const atPath = app.getPath ('desktop');
const { spawn } = require ('child_process');
let openTerminalAtPath = spawn ('open', [ '-a', 'Terminal', atPath ]);
openTerminalAtPath.on ('error', (err) => { console.log (err); });
It should be easy to adapt it to any selected atPath...
As for running other commands, I haven't found a way yet...
And here is the equivalent working code for Linux Mint Cinnamon or Ubuntu:
const { app } = require ('electron').remote;
const terminal = 'gnome-terminal';
const atPath = app.getPath ('desktop');
const { spawn } = require ('child_process');
let openTerminalAtPath = spawn (terminal, { cwd: atPath });
openTerminalAtPath.on ('error', (err) => { console.log (err); });
Please note that the name of the terminal application may be different, depending on the Linux flavor (for instance 'mate-terminal' on Linux Mint MATE), and also that the full path to the application can be explicitly defined, to be on the safe side:
const terminal = '/usr/bin/gnome-terminal';
HTH...

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

Error: spawn ENOENT on Windows

I'm on node v4.4.0 and on Windows 10. I'm using bunyan to log my node application.
try {
var fs = require('fs');
var path = require('path');
var spawn = require('child_process').spawn;
var through = require('through');
} catch (err) {
throw err;
}
var prettyStream = function () {
// get the binary directory of bunyan
var bin = path.resolve(path.dirname(require.resolve('bunyan')), '..', 'bin', 'bunyan');
console.log(bin); // this outputs C:\www\nodeapp\src\node_modules\bunyan\bin\bunyan, the file does exist
var stream = through(function write(data) {
this.queue(data);
}, function end() {
this.queue(null);
});
// check if bin var is not empty and that the directory exists
if (bin && fs.existsSync(bin)) {
var formatter = spawn(bin, ['-o', 'short'], {
stdio: [null, process.stdout, process.stderr]
});
// stream.pipe(formatter.stdin); // <- did this to debug
}
stream.pipe(process.stdout); // <- did this to debug
return stream;
}
The logging spits out in the console due to the fact I used stream.pipe(process.stdout);, i did this to debug the rest of the function.
I however receive the error:
Error: spawn C:\www\nodeapp\src\node_modules\bunyan\bin\bunyan ENOENT
at exports._errnoException (util.js:870:11)
at Process.ChildProcess._handle.onexit (internal/child_process.js:178:32)
at onErrorNT (internal/child_process.js:344:16)
at nextTickCallbackWith2Args (node.js:442:9)
at process._tickCallback (node.js:356:17)
at Function.Module.runMain (module.js:443:11)
at startup (node.js:139:18)
at node.js:968:3
I'm guessing this is a Windows error. Anyone have any ideas?
Use {shell: true} in the options of spawn
I was hit with this problem recently so decided to add my findings here. I finally found the simplest solution in the Node.js documentation. It explains that:
child_process.exec() runs with shell
child_process.execFile() runs without shell
child_process.spawn() runs without shell (by default)
This is actually why the exec and spawn behave differently. So to get all the shell commands and any executable files available in spawn, like in your regular shell, it's enough to run:
const { spawn } = require('child_process')
const myChildProc = spawn('my-command', ['my', 'args'], {shell: true})
or to have a universal statement for different operating systems you can use
const myChildProc = spawn('my-command', ['my', 'args'], {shell: process.platform == 'win32'})
Side notes:
It migh make sense to use such a universal statement even if one primairly uses a non-Windows system in order to achieve full interoperability
For full consistence of the Node.js child_process commands it would be helpful to have spawn (with shell) and spawnFile (without shell) to reflect exec and execFile and avoid this kind of confusions.
I got it. On Windows bunyan isn't recognized in the console as a program but as a command. So to invoke it the use of cmd was needed. I also had to install bunyan globally so that the console could access it.
if (!/^win/.test(process.platform)) { // linux
var sp = spawn('bunyan', ['-o', 'short'], {
stdio: [null, process.stdout, process.stderr]
});
} else { // windows
var sp = spawn('cmd', ['/s', '/c', 'bunyan', '-o', 'short'], {
stdio: [null, process.stdout, process.stderr]
});
}
I solved same problem using cross-spawn. It allows me to spawn command on both windows and mac os as one common command.
I think you'll find that it simply can't find 'bunyun', but if you appended '.exe' it would work. Without using the shell, it is looking for an exact filename match to run the file itself.
When you use the shell option, it goes through matching executable extensions and finds a match that way. So, you can save some overhead by just appended the executable extension of your binary.
I was having this same problem when trying to execute a program in the current working directory in Windows. I solved it by passing the options { shell: true, cwd: __dirname } in the spawn() call. Then everything worked, with every argument passed as an array (not attached to the program name being run).
I think, the path of bin or something could be wrong. ENOENT = [E]rror [NO] [ENT]ry

Resources