How to pass git pathspec args using node spawn - node.js

I am trying to run the following shell command from within a Node application:
git grep MySearchString -- 'MyPathToSearchFor'
This command runs successfully using's node's child_process.exec:
exec("git grep MySearchString -- 'MyPathToSearchFor'") // succeeds!
However, I cannot get the command working using child_process.spawn, which requires arguments to be provided as an array of strings.
// Spawn works without the pathspec args:
spawn('git', ['grep', 'MySearchString']) // success! exits with code 0
// It fails when the pathspec args are provided:
spawn('git', ['grep', 'MySearchString', '--', "'MyPathToSearchFor'"]) // exits with code 1
spawn('git', ['grep', 'MySearchString', "-- 'MyPathToSearchFor'"]) // exits with code 128
How can I provide spawn the -- MyPathToSearchFor args? How should I translate the dash-dash and pathspec args into spawn parameters?
The problem seems related to quotes in the args, but I'm not sure how to handle those.

The problem seems related to quotes in the args, but I'm not sure how to handle those.
Don't include quotes when you use the array argument form, e.g. just
spawn('git', ['grep', 'MySearchString', '--', "MyPathToSearchFor"])
With "'MyPathToSearchFor'" this makes git look for a file/directory whose name contains a single quote at the start and end (which in theory could exist, but rarely is what you have).

Related

Use ~ in std::process::Command

I am trying to use std::process::Command to run webdrivers programmatically installed at
$HOME/.webdrivers. I don't want users to have to add the directory to their path, so I was hoping to be able to use something like
let geckodriver = Command::new("~/.webdrivers/geckodriver")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
But this doesn't seem to work. I'm getting the error for the executable not being there.
thread 'main' panicked at 'Could not start geckodriver: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/lib.rs:49:10
Anything helps!
Tilde expansion is a feature of the shell (bash, etc.) - the OS facilities that std::process::Command use do not expand tildes. So you will have to do it yourself.
Get the HOME envvar using std::env::var_os, convert it to a Path, then join your executable path onto that.
As stargateur mentioned ~ is expanded by the shell. bash is a shell that can be used to perform the expansion. Instead of spawning geckodriver directly spawn bash to spawn geckodriver
let geckodriver = Command::new("bash")
.arg("-c")
.arg("~/.webdrivers/geckodriver")
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?;
Beware:
Packing the args into the string passed to Command::new("bash -c ~/.webdrivers/geckodriver") will not work since that will look for an executable with filename bash -c ~/.webdrivers/geckodriver (with spaces in the filename).

Wildcards in node child process [duplicate]

I want to execute a command like "doSomething ./myfiles/*.csv" with spawn in node.js. I want to use spawn instead of exec, because it is some kind of watch process and I need the stdout output.
I tried this
var spawn = require('child_process').spawn;
spawn("doSomething", ["./myfiles/*.csv"]);
But then the wildcard *.csv will not interpreted.
Is it not possible to use wildcards when using spawn()? Are there other possibilities to solve this problem?
Thanks
Torben
The * is being expanded by the shell, and for child_process.spawn the arguments are coming through as strings so will never get properly expanded. It's a limitation of spawn. You could try child_process.exec instead, it will allow the shell to expand any wildcards properly:
var exec = require("child_process").exec;
var child = exec("doSomething ./myfiles/*.csv",function (err,stdout,stderr) {
// Handle result
});
If you really need to use spawn for some reason perhaps you could consider expanding the wildcard file pattern yourself in Node with a lib like node-glob before creating the child process?
Update
In the Joyent Node core code we can observe an approach for invoking an arbitrary command in a shell via spawn while retaining full shell wildcard expansion:
https://github.com/joyent/node/blob/937e2e351b2450cf1e9c4d8b3e1a4e2a2def58bb/lib/child_process.js#L589
And here's some pseudo code:
var child;
var cmd = "doSomething ./myfiles/*.csv";
if ('win32' === process.platform) {
child = spawn('cmd.exe', ['/s', '/c', '"' + cmd + '"'],{windowsVerbatimArguments:true} );
} else {
child = spawn('/bin/sh', ['-c', cmd]);
}
Here's the simplest solution:
spawn("doSomething", ["./myfiles/*.csv"], { shell: true });
As #JamieBirch suggested in his comment, the key is telling spawn() to use the shell ({ shell: true }, see the docs), so the wildcard is properly resolved.
What OS are you using? In Unix-family OSs (e.g. Linux, MacOS), programs expect the shell process to expand wildcard filename arguments and pass the expansion in argv[]. In Windows OSs, programs usually expect to have to expand wildcards themselves (though only if they're Windows-native programs; ported Unix-family programs may at most try to run the arguments through a compatibility layer).
Your syntax looks like it's for a Unix-family system. If so, then when you call spawn() you're bypassing shell expansion, and your child process is going to treat dots and asterisks in arguments literally. Try using sh child_process in place of child_process and see if you get better results.

how to escape whitespace in Node-RED exec node command

Situation:
In my Node-RED flow there is a call to another program where I use the exec node.
The arguments are set-up in a function node and passed as the msg.payload to the exec node.
This works fine and returns the expected results as long as the command has no space in it.
Problem:
A customer cannot use the call to the command on his system, because the path tp the installation of the program contains a whitespace.
What I tried and didn't work:
Escaping the whitespace with ^ as read in another question: /opt/path^ toProgram^ directory/program
Escaping the whitespace with \\: /opt/path\\ toProgram\\ directory/program
Quoting the whole string with "": "/opt/path toProgram directory/program"
Quoting the whole string and escaping the whitespace: "/opt/path\\ toProgram\\ directory/program"
Quoting the whole string with '': '/opt/path toProgram directory/program'
Leaving the command line empty/""and combining any of the above points with its arguments to one string (in the function node that sets up the arguments for that exec node) and passing it on as the msg.payload -> input parameters in the exec node config panel. No Success.
What's not the problem:
The program itself works fine, on mine and on the customers system, it's only the path that is different
Other than that specific command string with whitespace, the configuration of the exec node and its msg.payload ( = input parameters or arguments) as well as the "use spawn() instead of exec()?" is fine and works
Request
Is there any other way to escape the whitespace that I'm not aware of so the path to the program can be found? From my understanding the whitespace is interpreted as a separator for the input arguments, which it should be, but not on the command string.
This should be a quick fix in my opinion, however nothing seems to work that usually works in node, js, php or bash.. thanks in advance for any hints and ideas!
Environment:
Node-RED version: v0.15.2 |
Node.js version: v5.12.0 |
Mac OS X 10.11.6 = Darwin 15.6.0 x64 LE
Screenshots
This is the part of the flow:
This config works:
This config does not work:
I've just tested this and option 3 (Quoting the path with "") works fine.
Putting "/home/foo/foo bar/date" in the command section of the exec node executed the binary correctly
After a good nights sleep the solution (at least what I thought at the time) was to adapt the exec node in node-red itself: The original code appended the additional arguments (the ones separated by whitespace) to the command, and then slice the whole string by whitespace into an array (node.cmd is the path to the command with or without whitespace):
before: var arg = node.cmd; arg += " "+msg.payload; arg = arg.match(/(?:[^\s"]+|"[^"]*")+/g);
and the (earlier, incomplete) solution was to prepend the command with the builtin array.unshift(<value>) function to the array after the slice operation, so that the command path with the whitespace is not affected by the array creation:
after: var arg = ""; ...add msg.payload and slice arg string into array.. arg.unshift(node.cmd);
The file can be found at: nodered node 75-exec.js on github, in the lines 46-51
Update to real solution:
After further investigation it was clear, that the Use spawn() instead of exec()? option was the problem: the command part with whitespace in double quotes and using exec() worked fine. And my approach couldn't be used when the command path contained an argument, e.g. in /home/foo bar/date -r 1000.
Following the issue at the official github repository of node-red the command part to the arguments array at the beginning after the slice but instead to have the command path with whitespace in quotes and later unwrapping them, copying the behaviour when exec() is used. He added this line in the original file after line 51: if (/^".*"$/.test(cmd)) { cmd = cmd.slice(1,-1); } and now both spawn() and exec() work the same - credits to him, unfortunately I don't know his username here.

Node js: Executing command line (opening a file)

I am trying to open a file through the command line by using node. I am using child_process.spawn, here is the code
var
process = require('child_process'),
cmd = process.spawn('cmd', ['start','tmp.txt'], {cwd: 'C:\\Users\\testuser\\Node_dev'});
I am expecting the file tmp.txt located in the Node_dev folder to be opened but I am getting the error -
dir error { [Error: spawn ENOENT] code: 'ENONENT', errno: 'ENOENT', syscall: 'spawn'
What am I missing?
Also, what's the difference between child_process.spawn vs child_process.exec for this?
I don't have an explanation for your ENOENT error [update: turns out this was a red herring], but one problem with your command is that you launch cmd.exe without /c, meaning that the shell spawned will (a) stay open and (b) actually ignore the specified command.
As for how the child_process module's methods differ:
.execFile and .exec take a callback that reports a single, buffered result, whereas .spawn provides chunked output via events.
Only .exec passes the command to the platform's default shell, which makes it more convenient, but less efficient.
In your case, you don't care about output returned from the spawned command, and since you need to involve the shell anyway,.exec is the best choice.
On Windows, use of start comes with its own pitfalls:
It is not an executable, but a shell builtin (to put it in Unix terms) - thus, it must be invoked via cmd.exe.
If its 1st argument is a [double-quoted] value with embedded spaces, it is interpreted as a window title rather than a filename argument; thus, to robustly pass a filename argument, you must pass an empty window title as the 1st argument.
Here are invocations that should work on Windows - note that both launch the child process asynchronously and ignore any output from it - all they do is to tell cmd.exe to open the specified file as if a user had opened it in Explorer:
using .exec:
var cpm = require('child_process');
// With .exec, specify the entire shell command as the 1st argument - it is implicitly
// passed to cmd.exe.
// '""' as the 1st argument to `start` is an empty window title that ensures that any
// filename argument with embedded spaces isn't mistaken for a window title.
cpm.exec('start "" "tmp.txt"', {cwd: 'C:\\Users\\testuser\\Node_dev'});
using .execFile or .spawn:
// With .spawn or .execFile, specify `cmd` as the 1st argument, and the shell command
// tokens as an array passed as the 2nd argument.
// Note the /c, which ensures that cmd exits after having executed the specified
// command.
// '""' as the 1st argument to `start` is an empty window title that ensures that any
// filename argument with embedded spaces isn't mistaken for a window title.
cpm.spawn('cmd', [ '/c', 'start', '""', 'tmp.txt' ], {cwd: 'C:\\Users\\testuser\\Node_dev'});
There is a cross-platform module on npm called open that opens files using the OS's default handler that you could use as a guide or just use it as-is.
To do it manually with spawn on Windows though, you'd do something like:
var args = ['/s', '/c', 'start', '', 'tmp.txt'],
opts = { cwd: 'C:\\Users\\testuser\\Node_dev' },
child = child_process.spawn('cmd.exe', args, opts);
child_process.exec() is basically a higher-level wrapper for child_process.spawn() which buffers output and then passes the buffered stdout and stderr output to a callback.
I am using the same, here is my command to run and executable with arguments:
var exec = require('child_process').spawn;
let scanProcess = exec('cmd', [ '/c', 'start', '""', 'feature.exe --version' ], {cwd: 'command_folder'});
I am running this on Azure DevOps in a container running Windows OS.
The above command just doesn't come back, it hangs. Any pointers.

Wildcards in child_process spawn()?

I want to execute a command like "doSomething ./myfiles/*.csv" with spawn in node.js. I want to use spawn instead of exec, because it is some kind of watch process and I need the stdout output.
I tried this
var spawn = require('child_process').spawn;
spawn("doSomething", ["./myfiles/*.csv"]);
But then the wildcard *.csv will not interpreted.
Is it not possible to use wildcards when using spawn()? Are there other possibilities to solve this problem?
Thanks
Torben
The * is being expanded by the shell, and for child_process.spawn the arguments are coming through as strings so will never get properly expanded. It's a limitation of spawn. You could try child_process.exec instead, it will allow the shell to expand any wildcards properly:
var exec = require("child_process").exec;
var child = exec("doSomething ./myfiles/*.csv",function (err,stdout,stderr) {
// Handle result
});
If you really need to use spawn for some reason perhaps you could consider expanding the wildcard file pattern yourself in Node with a lib like node-glob before creating the child process?
Update
In the Joyent Node core code we can observe an approach for invoking an arbitrary command in a shell via spawn while retaining full shell wildcard expansion:
https://github.com/joyent/node/blob/937e2e351b2450cf1e9c4d8b3e1a4e2a2def58bb/lib/child_process.js#L589
And here's some pseudo code:
var child;
var cmd = "doSomething ./myfiles/*.csv";
if ('win32' === process.platform) {
child = spawn('cmd.exe', ['/s', '/c', '"' + cmd + '"'],{windowsVerbatimArguments:true} );
} else {
child = spawn('/bin/sh', ['-c', cmd]);
}
Here's the simplest solution:
spawn("doSomething", ["./myfiles/*.csv"], { shell: true });
As #JamieBirch suggested in his comment, the key is telling spawn() to use the shell ({ shell: true }, see the docs), so the wildcard is properly resolved.
What OS are you using? In Unix-family OSs (e.g. Linux, MacOS), programs expect the shell process to expand wildcard filename arguments and pass the expansion in argv[]. In Windows OSs, programs usually expect to have to expand wildcards themselves (though only if they're Windows-native programs; ported Unix-family programs may at most try to run the arguments through a compatibility layer).
Your syntax looks like it's for a Unix-family system. If so, then when you call spawn() you're bypassing shell expansion, and your child process is going to treat dots and asterisks in arguments literally. Try using sh child_process in place of child_process and see if you get better results.

Resources