6

So as the title says, I would like to run a shell command on the same shell process/instance in C++ and communicate with it, how can I do that? I have looked up every corner and I couldn't find an appropriate/straight to the point answer. I am not a C++ overlord, my answer could be dumb. Now I could use a combination of fork/exec on unix, but I'm on windows. If there is a cross platform solution, please mention it below. Thanks in advance.

Pseudocode:

SHELL shell = make_shell();
shell.run("cd desktop");
shell.run("dir");
print(shell.stdout)
Dexus
  • 45
  • 1
  • 6
kenan238
  • 398
  • 4
  • 13
  • 4
    If the shell you're using provides an API that allows you to do that, you could use that. I'm not aware of any shell that does that though. Also, I don't believe you that fork/exec could achieve that job. I guess though that this is a so-called "XY problem", so it would help if you explained why you think to need exactly that. – Ulrich Eckhardt May 22 '22 at 13:03
  • @UlrichEckhardt well im making a program to switch between shells – kenan238 May 27 '22 at 13:43
  • 1
    You can control anything that communicates via stdin/stdout/stderr by creating pipes, launching the process connected to them and then doing IO on them. "redirect standard streams" would be a first search term I'd go for. C++ doesn't come with many tools for that, so I'd use Python for prototyping. BTW: That pseudocode actually improved your question a lot! – Ulrich Eckhardt May 27 '22 at 14:58
  • @UlrichEckhardt thanks! I'm getting better at asking. So about that, I can just create a pipe? If yes then should I use the windows api or is there an implementation in the standard library? I'll do some research on my end – kenan238 May 27 '22 at 15:10
  • Possible duplicate of [How can we execute multiple cmd commands in one session using C++ Lib Function or Windows API?](https://stackoverflow.com/questions/26358944/how-can-we-execute-multiple-cmd-commands-in-one-session-using-c-lib-function-o) Also I do too recommend you to check out the `system()` function. Here is a [link](https://www.tutorialspoint.com/system-function-in-c-cplusplus) . – Fetert May 27 '22 at 15:32
  • `std::system()` won't help, because you don't have any means of communication with the started shell. Especially you can't do what's described in the pseudocode above! – Ulrich Eckhardt May 27 '22 at 15:51
  • @kenan238 The C++ standard library does not provide anything for IPC like pipes, so you'll either have to use system APIs, or you could use an existing library that supports it (like Boost, for example, has IPC wrappers). But otherwise like Ulrich said, it should be as simple as creating a pipe for your `stdin`, and you could write the commands directly to that. – Human-Compiler May 27 '22 at 20:09
  • I seem to have blanked out your main requirement (_"the same shell instance"_) when I wrote my answer. I'll leave the answer up anyway since I think you'll find that avoiding the shell altogether will make your programs safer and more under your control. – Ted Lyngmo May 29 '22 at 06:45
  • Am I missing something, or is what you are asking simply impossible? You execute your program in a shell and the shell is blocked until your program exits. Any input via the keyboard gets potentially sent to your program after all (if you read from stdin). So executing commands in the very same shell process that runs your program is impossible while your program still runs. The shell is "blocked". Forking/exec on unix would create an entirely new process and execute the commands in that new process, not in the same shell. – Sedenion Jun 01 '22 at 20:12

3 Answers3

2

The standard library in C++ does not provide a way to do what you want. However, you can use a third party library, like boost::process, which will make the communication part between your program and the sub-processes pretty portable. If you want to execute built-in shell commands, or commands using the shell, you will still need to deal with differences in the shells used on the different platforms.

The boost::process library contains a lot of different ways of creating and communicating with sub processes (reference), both synchronously and asynchronously, so browse through the reference page to get a feeling for what it can do.

Here's a simple example that uses boost::process::system to run the command dir in the directory desktop and collects the output from that command. It does not use the shell (which should generally be avoided) but uses boost::process::search_path to find the command (in your PATH) and executes the command directly without involving a shell:

#include <boost/process.hpp>
#include <boost/process/start_dir.hpp>

#include <iostream>
#include <string>

int main() {
    namespace bp = ::boost::process;

    bp::ipstream out;
    bp::ipstream err;
    bp::system(bp::search_path("dir"),
               bp::start_dir = "./desktop",
               bp::std_out > out,           // collect stdout in out
               bp::std_err > err,           // collect stderr in err
               bp::std_in < stdin);         // for commands reading from stdin

    // display what was collected on stdout and stderr:
    std::string line;
    std::cout << "stdout capture:\n";
    while(std::getline(out, line)) {
        std::cout << '[' << line << "]\n";
    }

    std::cout << "\nstderr capture:\n";
    while(std::getline(err, line)) {
        std::cout << '[' << line << "]\n";
    }
}

Note: You need to link with boost_filesystem and boost_atomic for this example to work.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0

Linux: exec Windows: CreateProcessA

write ifdefs to switch based upon platform.

netskink
  • 4,033
  • 2
  • 34
  • 46
-1

I found this answer: https://stackoverflow.com/a/57096619/9282847.
It seems to be the only method which is both standard, and portable (assuming the implementation supports std::filesystem).

Like:

#include <filesystem>
#include <iostream>

int main() {
  namespace fs = std::filesystem;

  const auto kDesktopPath = fs::path("~/Desktop"); // portable filepath
  fs::current_path(kDesktopPath); // set CWD (This will throw on compiler explorer)

  auto dir_it = fs::recursive_directory_iterator{ fs::current_path() }; 
  for (auto& f: dir_it) {
    std::cout.put('\n') << f;
  }

}

Live example on compiler explorer

viraltaco_
  • 814
  • 5
  • 14