holzschu / ios_system

Drop-in replacement for system() in iOS programs
BSD 3-Clause "New" or "Revised" License
902 stars 147 forks source link

👻emmmm..... Can I realtime print the output line by line when executing `ios_popen()` ? #80

Open Chihpin opened 5 years ago

Chihpin commented 5 years ago

I am a newbie about this.

Here's my code

It waits for the command to finish before printing the output together


void my_popen(NSString *input, void(^output)(NSString *output, BOOL isEnd)) {

    const char *command = input.UTF8String;

    char result_buf[1024];
    int rc = 0;
    __block FILE *fp;

    fflush(stdout);
    fp = ios_popen(command, "r");

    if(NULL == fp)
    {
        perror("popen failed!");
        output(@"popen failed!", YES);
        return;
    }
    while(fgets(result_buf, sizeof(result_buf), fp) != NULL)
    {
        if('\n' == result_buf[strlen(result_buf)-1])
        {
            result_buf[strlen(result_buf)-1] = '\0';
        }
        output([NSString stringWithFormat:@"%s", result_buf], NO);
    }

    rc = pclose(fp);
    if(-1 == rc)
    {
        printf("【%s】【%d】\r\n", command, rc);
    }
    else
    {
        printf("【%s】【%d】【%d】\r\n", command, rc, WEXITSTATUS(rc));
    }
}
holzschu commented 5 years ago

So, does that work? You could also do it with the getline() function: getline(result_buf, 1024, fp);

Chihpin commented 5 years ago

@holzschu I'm sorry I didn't describe it clearly. The piece of code in my question doesn't work for realtime print

I hit a breakpoint and found it blocked on this line of code

fp = ios_popen(command, "r");

It took a few seconds to continue., so getline() is not work well.

Then I tried to change the internal implementation of ios_popen

void ios_popen2(const char* inputCmd, const char* type, FILE **output) {
    // Save existing streams:
    int fd[2] = {0};
    const char* command = inputCmd;
    // skip past all spaces
    while ((command[0] == ' ') && strlen(command) > 0) command++;
    // TODO: skip past "/bin/sh -c" and "sh -c"
    if (pipe(fd) < 0) { return; } // Nothing we can do if pipe fails
    // NOTES: fd[0] is set up for reading, fd[1] is set up for writing
    // fpout = fdopen(fd[1], "w");
    // fpin = fdopen(fd[0], "r");
    if (type[0] == 'w') {
        // open pipe for reading
        child_stdin = fdopen(fd[0], "r");
        // launch command:
        *output = fdopen(fd[1], "w");
        ios_system(command);
    } else if (type[0] == 'r') {
        // open pipe for writing
        // set up streams for thread
        child_stdout = fdopen(fd[1], "w");
        // launch command:
        *output = fdopen(fd[0], "r");
        ios_system(command);
    }
//    return NULL;
}

And then I call this in my app

__block FILE *fp = NULL;
__block BOOL end_flag = NO;
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    char result_buf[MAXLINE];
    fpos_t pos = 0;
    while(1)
    {
        if (fp != NULL) {
            fsetpos(fp, &pos);
            if (fgets(result_buf, sizeof(result_buf), fp) != NULL) {
                fgetpos(fp, &pos);
                if('\n' == result_buf[strlen(result_buf)-1])
                {
                    result_buf[strlen(result_buf)-1] = '\0';
                }
                output([NSString stringWithFormat:@"%s", result_buf], NO);
            }
        }
        if (end_flag) {
            break;
        }
        sleep(0.5);
    }
});
ios_popen2(input.UTF8String, "r", &fp);
end_flag = YES;

In this way, the real-time printout is realized. It's fantastic!

I don't know much about ios_system framework. emmm..... may I ask if there are any danger in my solution?

holzschu commented 5 years ago

Ah, now I understand the question. Yes, by default, ios_system commands run in the foreground if they are the first command to be called. Meaning the command runs entirely, then gives back control to the app. The solution to have interactive communication between the command and the application is to run them in queues or threads, as you have done.

You could (probably) also make it work by sending the ios_popen2 command in an async queue, and have the reading in the main loop.

You could also simplify your code with:

    thread_stdout = fdopen(fd[1], "w");
    thread_stdin =  fdopen(fd[0], "r");
    ios_system(command); 

and then listen to thread_stdout in the async queue. By default, ios_system commands write their standard output to a stream called thread_stdout and listen to a stream called thread_stdin.

Chihpin commented 5 years ago

@holzschu I see. thx