Cannot capture stdout output of child process of batch script - node.js

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?

Related

Can I access CLI programs from within Node's child_process?

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.

How to perform multiple windows command in cmd using python in same shell

I want to perform some couple of command one after one and store into variable in same shell. Whenever I try to perform next command it executes in new shell
import subprocess
cmd1 = 'cd C:\\Program Files (x86)\\openvpn\\bin\\'
output = subprocess.getoutput(cmd1) # it goes to the above directory
cmd2 = 'openvpn.exe --help'
output2 = subprocess.getoutput(cmd2)
At the cmd2 when it runs,a new shell perform this command and tells--
'openvpn.exe' is not recognized as an internal or external command,
operable program or batch file.
I want to perform couple of commands one after another and store into variables. So I can use that variables in other commands.
You should use the run method, like so:
output = subprocess.run(['openvpn.exe', '--help'], cwd='C:\\Program Files (x86)\\openvpn\\bin\\', capture_output=True)
cwd = current working directory (where should the command run)
capture_output = record the stdout, stderr streams
Then you can access your results within the stdout, stderr properties:
output.stdout # will give you back the output of the command.
You weren't getting any results because the cd command has no effect within subprocess. It has to do with the way cd works in the first place - no process can change another processes working directory.

How can I change actual shell cd with nodejs

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.

Multi-command shell AS detached child process WITH spawn() IN Node.js

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' ]);

Using konsole to emulate a terminal through Perl

I have an issue when using this command
system("konsole --new-tab --workdir<dir here> -e perlprogram.pl &");
It opens perlprogram.pl which has:
system("mpg321 song.mp3");
I want to do this because mpg321 stalls the main perl script. so i thought by opening it in another terminal window it would be ok. But when I do run the first script all it does is open a new tab and do nothing.
Am I using konsole correctly?
Am I using konsole correctly?
Likely, no. But that depends. This question can be decomposed into two issues:
How do I achieve concurrency, so that my program doesn't halt while I execute an external command
How do I use konsole.
1. Concurrency
There are multiple ways to do that. Starting with the fork||exec('new-program'), to system 'new-program &', or even open.
system will invoke the standard shell of your OS, and execute the command you provided. If you provide multiple arguments, no shell escaping is done, and the specified program execed directly. (The exec function has the same interface so far). system returns a number that specifies if the command ran correctly:
system("my-command", "arg1") == 0
or die "failed my-command: $?";
See perlfunc -f system for the full info on what this return value signifies…
The exec never returns if successfull, but morphs your process into executing the new program.
fork splits your process in two, and executes the child and the process as equal copies. They only differ in the return value of fork: The parent gets the PID of the child; the child gets zero. So the following executes a command asynchronously, and lets your main script execute without further delay.
my #command = ("mpg321", "song.mp3");
fork or do {
# here we are in the child
local $SIG{CHLD} = 'IGNORE'; # don't pester us with zombies
# set up environment, especially: silence the child. Skip if program is well-behaved.
open STDIN, "<", "/dev/null" or die "Can't redirect STDIN";
open STDOUT, ">", "/dev/null" or die "Can't redirect STDOUT";
exec {$command[0]} #command;
# won't ever be executed on success
die qq[Couldn't execute "#command"];
};
The above process effectively daemonizes the child (runs without a tty).
2. konsole
The command line interface of that program is awful, and it produces errors half the time when I run it with any parameters.
However, your command (plus a working directory) should actually work. The trailing ampersand isn't neccessary, as the konsole command returns immediately. Something like
# because I `say` hello, I can be certain that this actually does something.
konsole --workdir ~/wherever/ --new-tab -e perl -E 'say "hello"; <>'
works fine for me (opens a new tab, displays "hello", and closes when I hit enter). The final readline there keeps the tab open until I close it. You can keep the tab open until after the execution of the -e command via --hold. This allows you to see any error messages that would vanish otherwise.

Resources