normalize command before calling exec/spawn on windows? - node.js

Let's say I'm trying to call ffmpeg from the current directory. On linux I'd call exec or spawn with './ffmpeg'. In order for this code to be portable to windows do I need to strip off the './' or is that somehow taken care of for me?

This is just half an answer: welcome to anyone to modify it and expand it.
Here is the result of my digging in the source code:
The exec method does a small check of the platform (taken from the source):
if (process.platform === 'win32') {
file = 'cmd.exe';
args = ['/s', '/c', '"' + command + '"'];
options = util._extend({}, options);
options.windowsVerbatimArguments = true;
} else {
file = '/bin/sh';
args = ['-c', command];
}
I don't know how you have added ffmpeg to your PATH on Windows, so it really depends on your setup. In any case, simply pass to exec the command as if you would be running it from the cmd.exe shell.
I however could not find something similar for spawn.

Related

node child_process.spawn fails with ENOENT when using chained commands

I'm relatively new to TypeScript/Javascript/Node.js, but I have to execute a binary myapp (or myapp.exe on Windows) after I have sourced an environment script myapp_env (run myapp_env.bat on Windows)
. myapp_env && myapp
or in windows (cmd)
myapp_env.bat && myapp.exe
I am trying to use spawn:
import {exec,spawn} from 'child_process';
import {exists} from 'fs'
let programhome: string = 'C:/SoftwareAG105/Apama';
let envscript: string = programhome + '/bin/apama_env.bat';
let program: string = programhome + '/bin/correlator.exe';
exists(envscript , found =>
console.log( envscript + (found ? " is there" : " is not there")));
exists(program , found =>
console.log( program + (found ? " is there" : " is not there")));
try {
let test = spawn(envscript + ' && ' + program);
test.stdout.on('data', data => console.log(data));
test.stderr.on('data', data => console.log(data));
test.on('error', data => console.log('ERROR ' + data));
test.on('end', data => console.log('END ' + data));
test.on('exit', data => console.log('Exit ' + data));
} catch (error) {
console.log(error);
}
I get an ENOENT error which I presume is due to it trying to see whether the entire string exists as a file(?). If I run them individually then it works fine. In both cases the exists line will print "is there"....
Edit
After samuels answer I changed the following lines lines
...
import {parse,format,join} from 'path'
...
let programhome: string = join( 'C:' , 'SoftwareAG105', 'Apama' );
let envscript: string = join( programhome ,'bin','apama_env.bat');
let program: string = join(programhome , 'bin' , 'correlator.exe');
exists(envscript , found => console.log( envscript + (found ? " is there" : " is not there")));
exists(program , found => console.log( program + (found ? " is there" : " is not there")));
ERROR Error: spawn C:\SoftwareAG105\Apama\bin\apama_env.bat &&
C:\SoftwareAG105\Apama\bin\correlator.exe ENOENT index.js:15
C:\SoftwareAG105\Apama\bin\apama_env.bat is there index.js:9
C:\SoftwareAG105\Apama\bin\correlator.exe is there
TLDR; So my question is can I chain commands in spawn so that I can source the environment and run the program?
Ok so finally I found the information buried deep in various google posts :
I found that exec would work the way I wanted, but spawn would not, it turns out that for exec a shell is started allowing the chaining to occur.
https://www.freecodecamp.org/news/node-js-child-processes-everything-you-need-to-know-e69498fe970a/
By default, the spawn function does not create a shell to execute the
command we pass into it. This makes it slightly more efficient than
the exec function, which does create a shell. The exec function has
one other major difference. It buffers the command’s generated output
and passes the whole output value to a callback function (instead of
using streams, which is what spawn does).
child_process.spawn takes an options object which can have a property called shell
let test = spawn(envscript + ' && ' + program,{shell:true});
This extra configuration allows me to do the chaining I needed and so I can now source the environment and run the program correctly.
That might have something to do with the path delimiter on Windows being a backslash instead of a forward slash.
When building your strings, try to use path.delimiter (imported from the default node path module) (which is either / or \ depending on the OS). Like so :
let programhome: string = ['C:', 'SoftwareAG105', 'Apama'].join(path.delimiter);.
This way your path will always be valid, regardless of the OS.

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...

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

node child_process.spawn not working with spaces in path on windows

How to provide a path to child_process.spawn
For example the path:
c:\users\marco\my documents\project\someexecutable
The path is provided by the enduser from a configuration file.
var child_process = require('child_process');
var path = require('path');
var pathToExecute = path.join(options.toolsPath, 'mspec.exe');
child_process.spawn(pathToExecute, options.args);
Currently only the part after the space is used by child_process.spawn
I also tried by adding quotes arround the path like this:
var child_process = require('child_process');
var path = require('path');
var pathToExecute = path.join(options.toolsPath, 'mspec.exe');
child_process.spawn('"' + pathToExecute + '"', options.args);
However this results in a ENOENT error.
The first parameter must be the command name, not the full path to the executable. There's an option called cwd to specify the working directory of the process, also you can make sure the executable is reachable adding it to your PATH variable (probably easier to do).
Also, the args array passed to spawn shouldn't contain empty elements.
You code should look something like this:
child_process.spawn('mspec.exe', options.args, {cwd: '...'});
As per https://github.com/nodejs/node/issues/7367#issuecomment-229728704 one can use the { shell: true } option.
For example
const { spawn } = require('child_process');
const ls = spawn(process.env.comspec, ['/c', 'dir /b "C:\\users\\Trevor\\Documents\\Adobe Scripts"'], { shell: true });
Will work.
I am using spawn frequently, the way I solved the problem is to use process.chdir. So if your path is c:\users\marco\my documents\project\someexecutable then you should do the following:
process.chdir('C:\\users\\marco\\my documents\\project');
child_process.spawn('./myBigFile.exe', options.args);
Note the double \s, that's how it worked for me.

Move files with node.js

Let's say I have a file "/tmp/sample.txt" and I want to move it to "/var/www/mysite/sample.txt" which is in a different volume.
How can i move the file in node.js?
I read that fs.rename only works inside the same volume and util.pump is already deprecated.
What is the proper way to do it? I read about stream.pipe, but I couldn't get it to work. A simple sample code would be very helpful.
Use the mv module:
var mv = require('mv');
mv('source', 'dest', function(err) {
// handle the error
});
If on Windows and don't have 'mv' module, can we do like
var fs = require("fs"),
source = fs.createReadStream("c:/sample.txt"),
destination = fs.createWriteStream("d:/sample.txt");
source.pipe(destination, { end: false });
source.on("end", function(){
fs.unlinkSync("C:/move.txt");
});
The mv module, like jbowes stated, is probably the right way to go, but you can use the child process API and use the built-in OS tools as an alternative. If you're in Linux use the "mv" command. If you're in Windows, use the "move" command.
var exec = require('child_process').exec;
exec('mv /temp/sample.txt /var/www/mysite/sample.txt',
function(err, stdout, stderr) {
// stdout is a string containing the output of the command.
});
You can also use spawn if exec doesn't work properly.
var spawn = require("child_process").spawn;
var child = spawn("mv", ["data.csv","./done/"]);
child.stdout.on("end", function () {
return next(null,"finished")
});
Hope this helps you out.

Resources