2

I'm trying to call a shell script from C++ with custom input. What I could do is:

void dostuff(string s) {
    system("echo " + s + " | myscript.sh");
    ...
}

Of course, escaping s is quite difficult. Is there a way that I can use s as stdin for myscript.sh? Ie, something like this:

void dostuff(string s) {
    FILE *out = stringToFile(s);
    system("myscript.sh", out);
}

2 Answers 2

2

A simple test to reassign stdin and restore it after the system call:

#include <cstdlib>     // system
#include <cstdio>      // perror
#include <unistd.h>    // dup2
#include <sys/types.h> // rest for open/close
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include <iostream>

int redirect_input(const char* fname)
{
    int save_stdin = dup(0);

    int input = open(fname, O_RDONLY);

    if (!errno) dup2(input, 0);
    if (!errno) close(input);

    return save_stdin;
}

void restore_input(int saved_fd)
{
    close(0);
    if (!errno) dup2(saved_fd, 0);
    if (!errno) close(saved_fd);
}

int main()
{
    int save_stdin = redirect_input("test.cpp");

    if (errno)
    {
        perror("redirect_input");
    } else
    {
        system("./dummy.sh");
        restore_input(save_stdin);

        if (errno) perror("system/restore_input");
    }

    // proof that we can still copy original stdin to stdout now
    std::cout << std::cin.rdbuf() << std::flush;
}

Works out nicely. I tested it with a simple dummy.sh script like this:

#!/bin/sh
/usr/bin/tail -n 3 | /usr/bin/rev

Note the last line dumps standard input to standard output, so you could test it like

./test <<< "hello world"

and expect the following output:

won tuodts ot nidts lanigiro ypoc llits nac ew taht foorp //    
;hsulf::dts << )(fubdr.nic::dts << tuoc::dts    
}
hello world
3
  • I would, except I'd really prefer not to do linear- (or, if I'm particularly lazy, quadratic-) time allocation just for that. Also the code will be annoying.
    – joshlf
    Commented Oct 6, 2012 at 23:07
  • @joshlf13 Well, I get that (allthough your question implies short input, due to the code sample you gave). I have added a little example of what I had in mind. This works nicely for me
    – sehe
    Commented Oct 6, 2012 at 23:22
  • 1
    Update: included restoring stdin back to original values. This proof of concept works rather well. I hope you like it
    – sehe
    Commented Oct 6, 2012 at 23:34
0

Use popen:

void dostuff(const char* s) {
  FILE* f = fopen(s, "r");
  FILE* p = popen("myscript.sh", "w");
  char buf[4096];
  while (size_t n = fread(buf, 1, sizeof(buf), f))
    if (fwrite(buf, 1, n, p) < n)
      break;
  pclose(p);
}

You'll need to add error checking to make this robust.

Note that I prefer a const char*, since it is more flexible (works with things other than std::string) and matches what's going on inside. If you really prefer std::string, do it like so:

void dostuff(const std::string& s) {
    FILE* f = fopen(s.c_str(), "r");
    ⋮

Also note that the 4096-byte buffer was chosen because it matches the page size on most systems. This isn't necessarily the most efficient approach, but it'll be fine for most purposes. I've found 32 KiB to be a sweet spot in my own unscientific tests on a laptop, so you might want to play around, but if you are serious about efficiency, you'll want to switch to asynchronous I/O, and start readn+1 immediately after initiating writen.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.