For reference: https://nodejs.org/api/child_process.html#child_process_options_detached
Hey guys,
So I need to spawn a child child-process, spawn because exec doesn't allow a options.detached & child.unref();, meaning it can be decoupled with the parent allowing the child to run and finish on it's own, vice-versa also the parent (in our particular case, the parent process can die before a long-running child, update in that case, is finished without needing to wait for the child like with exec).
We have a long connected ("… ; … ; …") command that is built by the node (parent) app, but as like spawn("echo stuff >>stderr.log") doesn't work, only spawn('ls', [-l]), I obviously can't chain commands (as it's also referenced in he docu and multiple times on SO.
TLDR;
We need to use spawn, but spawn can't process chained shell commands.
Do I really now need to write my command in a bash and execute that then, is this REALLY the ONLY option??
THX
Notice the shell option for spawn:
If true, runs command inside of a shell. Uses '/bin/sh' on UNIX, and 'cmd.exe' on Windows. A different shell can be specified as a string. The shell should understand the -c switch on UNIX, or /d /s /c on Windows. Defaults to false (no shell).
So:
let child = child_process.spawn('foo; bar; blah', { shell : true });
EDIT: if you're using a Node version that doesn't support this option, here's an alternative:
let child = child_process.spawn('/bin/sh', [ '-c', 'foo; bar; blah' ]);
Related
Using Node.js, I'm writing a Windows Batch script which calls some external programs. I want to execute this script and capture its output (and that of its children) to be able to display it inside an HTML container (I'm writing an Electron application). On Linux, I generate a Bash script that runs perfectly fine and whose output I can capture correctly. However, once the Windows Batch script spawns its child processes, I'm unable to capture their stdout and stderr; the only output child.stdout.on ("data", () => { /* ... */ }); catches is that of the Batch script itself.
I have tried various variants of how the Batch script calls the external programs, so far, neither has had the desired effect: all of them opened a new cmd.exe window. Using the advice from start /?, I am currently calling the programs as follows:
start /b /wait PROGRAM ARGUMENTS
(Where PROGRAM for example is pdflatex.exe.) By appending 2>&1 I was able to suppress new cmd.exe windows by running the batch script out of a cmd.exe window, but not by calling it from Node.js. I'm using the following code to call the Batch script:
const { spawn } = require ("child_process");
var child = spawn (
"call",
[ "path/to/batch.bat" ],
{
detached: true,
shell: true
}
);
Directly calling the Batch script by using the path to it as the command argument of spawn (); did not have the desired effect either.
Another method of invoking the Batch script and external programs I have tried but which did not have the desired result either, was to directly call the external programs from the Batch script like
C:\Full\Path\To\pdflatex.exe -halt-on-error -output-format=pdf file.tex
and using the Batch script's full path as the command argument to spawn (); like so:
const { spawn } = require ("child_process");
var child = spawn (
"C:/Full/Path/To/Batch.bat",
[ ],
{
detached: true,
shell: true
}
);
This did, like all other methods, capture the output of the cmd.exe process interpreting the script but opened a new cmd.exe window for all external programs called by the Batch script. The only other contents of the Batch script are along the lines of:
#echo off
REM Cd into the desired working directory
CD /D "C:\Path\To\Cwd"
REM Execute external programs like pdflatex:
C:\Full\Path\To\pdflatex.exe -halt-on-error -output-format=pdf file.tex
Nothing in the Batch script should actually interfere with how the commands are handled, but obviously this is not the case.
I am fine with modifying the call to spawn (); for Windows only; however, I must run the external programs using a Batch script and not using spawn (); directly because there is a requirement for backward compatibility I must meet.
So, after all, how would I be able to capture grandchild processes' stdout using a Batch script in Node.js?
I’ve written a node script that cd’s into multiple directories in sequence and executes bash commands in order to deploy the contents of the repos to my development environment.
Native bash commands work fine, like cd, ls, but it looks like the subshell or child process (or whatever the proper term is, I don’t understand the inner workings of Bash) that’s opened by node doesn’t have anything available to my normal prompt.
E.g.
the custom bash toolset that’s available globally
nvm (is this even possible, to run a different version of node within a node subshell?)
gulp breaks because it doesn't have the necessary node version installed.
Is it possible to access these programs/commands from the node subshell? I’m using the child_process node module.
const { exec } = require('child_process');
function command (command) {
exec (command, (err, stdout, stderr) => {
if (err) {
error(err);
} else {
message(`stdout: ${stdout}`);
message(`stderr: ${stderr}`);
}
});
}
Used as in:
command('nvm use 6');
command('gulp build');
command('pde deploy');
The child process is not run as bash. child_process spawns the executable using the regular sh shell.
If you need the commands to run within bash, the command line you run needs to be wrapped in bash -c. For example:
command('bash -c "my command here"');
Also, each command you run is a sub-process, which does not affect the parent process, nor any subsequent sub processes. Thus, a shell built-in like cd will only change the directory for that sub-process, which immediately goes away. You will see this if you run:
command('cd /');
command('ls');
The ls command will show the current working directory, not the root directory.
If you run your command with bash -c and the $PATH and other environment variables still aren't set up the way you need them, you need to debug your shell start-up scripts. Perhaps there's a difference between interactive shells (.bash_profile) and all shells (.bashrc).
Note that fully non-interactive shells may need to explicitly have the start-up script you want to run specified.
I am creating a command line program to act as a shortcut for my environment. Commands like $ cd $enterprise/products/<product> are so common, that I like to compile into: $ enterprise product.
My problem is: I cannot change shell directory directly, like running $ cd $enterprise/products/<product> with process.chdir.
Using console.log(process.cwd()) shows that the directory has changed, but not on shell, only on nodejs internal process(I think it's running on a own shell).
Typing $ pwd in shell shows that I still are on the same folder.
I was searching for a solution, like a shell script that interprets the output of a nodejs file, then source the output.
Thanks.
This is actually bit trickier than it sounds.
You can't just change the working directory of the shell that is running the process, without making assumptions about which shell it is or the OS (I personally use applescript to spawn new terminal tabs).
What we can do, however, is spawn a new shell!
let child_process = require('child_process');
child_process.spawn(
// With this variable we know we use the same shell as the one that started this process
process.env.SHELL,
{
// Change the cwd
cwd: `${process.cwd()}/products/${product_id}`,
// This makes this process "take over" the terminal
stdio: 'inherit',
// If you want, you can also add more environment variables here, but you can also remove this line
env: { ...process.env, extra_environment: 'some value' },
},
);
When you run this, it seems like you cd into a directory, but actually you are still running inside nodejs!
You can't do this; every child process has its own working directory inherited from the parent. In this case, your cd gets its working directory from its parent (your shell). A child process can't change the directory – or any other state – of the parent process.
I would like to execute any bash command. I found Command::new but I'm unable to execute "complex" commands such as ls ; sleep 1; ls. Moreover, even if I put this in a bash script, and execute it, I will only have the result at the end of the script (as it is explain in the process doc). I would like to get the result as soon as the command prints it (and to be able to read input as well) the same way we can do it in bash.
Command::new is indeed the way to go, but it is meant to execute a program. ls ; sleep 1; ls is not a program, it's instructions for some shell. If you want to execute something like that, you would need to ask a shell to interpret that for you:
Command::new("/usr/bin/sh").args(&["-c", "ls ; sleep 1; ls"])
// your complex command is just an argument for the shell
To get the output, there are two ways:
the output method is blocking and returns the outputs and the exit status of the command.
the spawn method is non-blocking, and returns a handle containing the child's process stdin, stdout and stderr so you can communicate with the child, and a wait method to wait for it to cleanly exit. Note that by default the child inherits its parent file descriptor and you might want to set up pipes instead:
You should use something like:
let child = Command::new("/usr/bin/sh")
.args(&["-c", "ls sleep 1 ls"])
.stderr(std::process::Stdio::null()) // don't care about stderr
.stdout(std::process::Stdio::piped()) // set up stdout so we can read it
.stdin(std::process::Stdio::piped()) // set up stdin so we can write on it
.spawn().expect("Could not run the command"); // finally run the command
write_something_on(child.stdin);
read(child.stdout);
I'have a process created with a spawn in nodejs with the option shell:true so the process starts in a real shell. So when I try to kill this process with streamingTask.kill() it's not working. Without the option shell:true everything works fine.
This is how my code looks:
var options = {shell:true};
streamingTask = spawn('gst-launch-1.0',args,options);
...
streamingTask.kill()
So how can I kill this process now?
This doesn't work because you are killing the shell process itself, not the child process(es) spawned by the shell (gst-launch-1.0 in your case).
There's a package on npm called tree-kill that provides an easy one-line solution:
var kill = require('tree-kill');
kill(streamingTask.pid);