Is there a way to pipe IO? - crystal-lang

I'm trying to pipe 2 IO object, i come from nodejs and we can do something like that:
const child_process = require('child_process')
const shell = child_process.spawn('/bin/sh')
shell.stdout.pipe(process.stdout)
shell.stdin.write('pwd\n')
shell.stdin.write('ls\n')
/* write all command i want */
and im looking for do the same thing in crystal
i know for the current example we can write
shell = Process.new("/bin/sh", input: Process::Redirect::Pipe, output: STDOUT, error: STDOUT)
shell.input << "ls\n"
shell.input << "pwd\n"
# all commands i want
but for some reason passing TCPSocket to Process.new input/output/error dont work very well (see here too if you have time Process and TCPSocket not close properly in crystal)
so im looking for an alternative way who will look like:
shell = Process.new("/bin/sh", input: Process::Redirect::Pipe, output: Process::Redirect::Pipe, Process::Redirect::Pipe)
shell.output.pipe(STDOUT) # not the crystal pipe but like the nodejs pipe
shell.input << "ls\n"
shell.input << "pwd\n"
# all commands i want

You can use IO.copy inside a coroutine:
shell = Process.new("/bin/sh", input: :pipe, output: :pipe, error: :pipe)
spawn { IO.copy shell.output, STDOUT }
spawn { IO.copy shell.error, STDERR }
shell.input << "ls /\n"
shell.input << "pwd\n"
shell.wait
https://carc.in/#/r/75z4

Related

Executing the shell command in QProcess.Piping the input

I am trying to pipe the commands and execute it, but I am not able to figure how to pipe it.
I am trying to copy multiple files at once using the shell command
for %I in (source) do copy %I (destination)
QString files = "for %I in (source) do copy %I (destination)"
QProcess copy ;
copy.start(files);
I have to implement the piping to do that.
for Eg.
QProcess sh;
sh.start("sh", QStringList() << "-c" << "ifconfig | grep inet");
sh.waitForFinished();
QByteArray output = sh.readAll();
sh.close();
How can I implement piping for my copy process?
Try this example:
QProcess sh;
sh.start( "sh", { "-c", "ifconfig | grep inet" } );
if ( !sh.waitForFinished( -1 ) )
{
qDebug() << "Error:" << sh.readAllStandardError();
return -1;
}
const auto output = sh.readAllStandardOutput();
// ...
waitForFinished() should be called in blocking mode and it must be checked if it was successful or not.

Boost.Process: captured stdout is buffered until size X

I am using boost.process to start another process.
I want to capture the stdout and print it myself.
The problem is, that the output is only printed in chunks or when stopping the subprocess.
The test subprocess is a python script which calls echo "test" 20 times per second.
void ExternalAppLauncher::setup()
{
boost::process::pipe stdout_p = boost::process::create_pipe();
boost::process::pipe stderr_p = boost::process::create_pipe();
{
file_descriptor_sink stdout_sink(stdout_p.sink, close_handle);
file_descriptor_sink stderr_sink(stderr_p.sink, close_handle);
file_descriptor_source stdout_source(stdout_p.source, close_handle);
file_descriptor_source stderr_source(stderr_p.source, close_handle);
out_stream.open(stdout_source);
err_stream.open(stderr_source);
childProcess.reset(new child(execute(
set_args(args),
bind_stdout(stdout_sink),
bind_stderr(stderr_sink),
inherit_env(),
// Guarantees that the child process gets killed, even when this process recieves SIGKILL(9) instead of SIGINT(2)
on_exec_setup([](executor&)
{
::prctl(PR_SET_PDEATHSIG, SIGKILL);
})
)));
}
}
// runs in another thread
void ExternalAppLauncher::run()
{
std::string s;
while (std::getline(err_stream, s))
{
std::cout << s;
}
}
This prints the output only every 10 seconds, probably because the buffer needs to be full before it is forwared?
When I dont call bind_stdout() the output appears immediately on the console.
What could solve this problem?
Thanks!
As I found out in the thread How to capture standard out and print to both the console and a file during process runtime (C++/Boost.Process) the python script I ran was the culprit.
Deactivating the buffering for the python script with export PYTHONUNBUFFERED=1 solved it.
The env var can be passed in to the application with set_env:
childProcess.reset(new child(execute(
set_args(args),
start_in_dir(workingDir),
bind_stdout(stdout_sink),
bind_stderr(stderr_sink),
inherit_env(),
set_env(std::vector<std::string> {"PYTHONUNBUFFERED=1"}))));

Qt: Failed to find out application pid when run from binary using script

I have an aim to obtain the PID of application, when the latter runs.
I wrote a simple function calls pgrep command:
QString Scriptlauncher::getAppProcessId() {
QProcess p;
QString programme("pgrep");
QStringList args = QStringList() << "app_name";
p.start(programme, args);
p.waitForReadyRead();
QByteArray rdata = p.readAllStandardOutput();
qDebug() << "------------- script output rawdata is:" << rdata;
if (!rdata.isEmpty()) {
QString pid(rdata);
pid = pid.left(pid.length() -1); // cut '\n' symbol
qWarning() << "APPLICATION pid is" << pid;
return pid;
}
qWarning() << "failed to find out PID";
return ("-1");
}
When I run the program directly from Qt or using a simple script (call it execute.sh; it exports all needed shared libs and then run app binary, - to run the app from terminal), the codeblock from above returns correct value:
user#host:/standalone_package/ execute.sh
------------- script output rawdata is: "21094\n"
APPLICATION pid is "21094"
But when I run execute.sh from the valgrind heap profiler command, the function returns:
user#host:/standalone_package/ valgrind --tool=massif --trace-children=yes ./execute.sh
------------- script output rawdata is: ""
failed to find out PID
Thanks to Hayt for links! I've found a solution!
The second link offered requires the instance of QProcess object. I have no idea about how to get it.
But using first link, I get the code working both directly from app and under valgrind:
QString Scriptlauncher::getAppProcessId() {
long pid = (long)getpid();
qDebug ("------------- pid is %d", pid);
QString pidstr = QString::number(pid);
return pidstr;
}

Query with QProcess

I'm supposed to check whether the service is RUNNING. I've a problem with QProcess query execution, when it comes to executing the following query: SC QUERY "service name" | findstr RUNNING, though this works fine when executed directly in command line in Windows. The code snipet here as follows:
QProcess process;
process.setProcessChannelMode(QProcess::ForwardedChannels);
process.start("SC QUERY \"Service_name\" | findstr RUNNING", QIODevice::ReadWrite);
// Wait for it to start
if(!process.waitForStarted())
return 0;
QByteArray buffer;
while(process.waitForFinished())
buffer.append(process.readAll());
qDebug() << buffer.data();
Output is:
Can you help me?
It is because using these three lines will not give you the expected results:
QProcess process;
process.setProcessChannelMode(QProcess::ForwardedChannels);
process.start("SC QUERY \"Service_name\" | findstr RUNNING", QIODevice::ReadWrite);
Based on the official documentation, QProcess is supposed to work for pipe'd commands:
void QProcess::setStandardOutputProcess(QProcess * destination)
Pipes the standard output stream of this process to the destination process' standard input.
In other words, the command1 | command2 shell command command can be achieved in the following way:
QProcess process1;
QProcess process2;
process1.setStandardOutputProcess(&process2);
process1.start("SC QUERY \"Service_name\"");
process2.start("findstr RUNNING");
process2.setProcessChannelMode(QProcess::ForwardedChannels);
// Wait for it to start
if(!process1.waitForStarted())
return 0;
bool retval = false;
QByteArray buffer;
while ((retval = process2.waitForFinished()));
buffer.append(process2.readAll());
if (!retval) {
qDebug() << "Process 2 error:" << process2.errorString();
return 1;
}
qDebug() << "Buffer data" << buffer;

Stay in directory with popen

I want to make some C++ program and I'm using function popen here to send commands to command line in Unix. It works fine, but when I call cd directory, the directory doesn't change. I thing that it's same when I try to run cd directory in some script, after finishing script directory path change back. So, scripts I must run like . ./script.sh not ./sript.sh, but how to do that with popen function? I have tried to add ". " before first argument of popen, but running ". ls" makes error.
Code:
cout << "# Command from " << session->target().full() << ": " << message.body() << endl;
//cout << "Prisla zprava" << endl;
//m_session->send( "Hello World", "No Subject" );
//system( message.body().c_str() );
//if ( message.body() == "" )
FILE* outp;
char buffer[100];
string outps = "";
outp = popen( message.body().c_str(), "r" );
while ( !feof(outp) )
{
fgets( buffer, 100, outp );
outps = outps + buffer;
}
pclose(outp);
cout << "& Output from command: " << outps << endl;
m_session->send( outps.c_str(), "Output" );
In message.body(); is string which I want to run (I'm receiving this from XMPP). When the string is for example "ls", it returns string with list of files in actual directory. But when the message is "cd directory", nothing happens, like trying to change directory in scripts.
Typically, the way the popen() command executes the command is via the shell. So, it opens a pipe, and forks. The child does some plumbing (connecting the pipe to the standard input or standard output - depending on the flag) and then executes
execl("/bin/sh", "/bin/sh", "-c", "what you said", (char *)0);
So, how it all behaves is going to depend on your key environment variables - notably PATH.
If you want to execute a script in the current directory, then one of these options:
outp = popen("./script.sh", "r");
outp = popen("sh -x ./script.sh", "r");
outp = popen("sh -c './script.sh arg1 arg2'", "r");
If you want to execute the 'ls' command:
outp = popen("/bin/ls /the/directory", "r");
And if you want to change directory before running something:
outp = popen("cd /somewhere/else; ./script", "r");
And so on...
If you want to change the directory of the program that is using popen(), then you need to use the 'chdir()' system call (or possibly fchdir()). If you think you might want to get back to where you started, use:
int fd = open(".", O_RDONLY);
chdir("/some/where/else");
...do stuff in new directory
fchdir(fd);
(Clearly, you need some error checking in that lot.)
It seems you have a bit of code that you do not understand. You are reading from outp, a pipe. Naming an input pipe outp is rather confusing. You then take the string you've read and pass it to m_session->send().
Nowhere in this whole process are you interacting with the Unix command line. In particular, popen() is not.