You are not logged in.

#1 2013-03-10 21:05:53

deepsoul
Member
From: Earth
Registered: 2012-12-23
Posts: 67
Website

Piping into pager (less) from C program

I am trying to write output to "less" from a C program to allow paging.  I have tried using popen (3), similar as in the following simple test program:

#include <stdio.h>

int main(int argc, char **argv)
{
    FILE *out;
    int i;

    out= popen("/usr/bin/less", "w");
    for( i= 0; i< 512; ++i )
        fprintf(out, "This is line %d\n", i);
    pclose(out);
}

This mostly works, but behaves differently from other programs that do the same thing.  In particular, you cannot use Ctrl-C to stop skipping to the end of the output without killing both less and the original program.  (Apparently less normally catches the signal, but is prevented from doing so when I start it as above.)

Both when typing "| less" in the shell and when using "man", this works.  I have looked at the output of pstree and noticed the following differences:  When piping into less from the shell, the less process is a child of the shell, so this is clearly different from what I can get with popen().  But when man is running, the less process is also a child of man, but it still behaves correctly.

Has anyone done this kind of thing before or has any ideas?


Officer, I had to drive home - I was way too drunk to teleport!

Offline

#2 2013-03-11 14:25:59

Lekensteyn
Member
From: Netherlands
Registered: 2012-06-19
Posts: 192
Website

Re: Piping into pager (less) from C program

You want to play with pipe/fork:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

void work(void) {
	int i;

	for (i=0; i<512; i++) {
		printf("Line %d\n", i);
	}
}

int main(void) {
	int pfds[2];
	pid_t pid;

	if (pipe(pfds) < 0) {
		perror("pipe failed");
		return EXIT_FAILURE;
	}

	pid = fork();
	if (pid < 0) {
		perror("fork failed");
		return EXIT_FAILURE;
	} else if (pid == 0) { /* child */
		close(pfds[0]); /* close unused read end */

		/* set write end of pipe as stdout for this child process */
		dup2(pfds[1], STDOUT_FILENO);
		close(pfds[1]);

		work();
	} else /* if (pid > 0) */ { /* parent */
		char *args[] = { "less", NULL };

		close(pfds[1]); /* close unused write end */

		/* set read end of pipe as stdin for this process */
		dup2(pfds[0], STDIN_FILENO);
		close(pfds[0]); /* already redirected to stdin */

		execvp(args[0], args);

		perror("exec failed");
		exit(EXIT_FAILURE);
	}
	return EXIT_SUCCESS;
}

Hint for the future, you can use strace to observe how programs work. For pipes:

strace -o log.txt -f sh -c 'echo 1337 | less'

(`-o log.txt` writes to that file, `-f` takes forks into account)

Offline

#3 2013-03-11 23:16:33

deepsoul
Member
From: Earth
Registered: 2012-12-23
Posts: 67
Website

Re: Piping into pager (less) from C program

If I understand you correctly, I will not get what I want using popen().  Pity, it would have been a nice shortcut.  I can see that in your code, the parent process exec's "less" and the child process generates the text, which is the opposite of how popen is  documented to work.  I will try your version out soon, thank you!  And thanks for reminding me of strace.

Edit: Lekensteyn's solution worked as I wanted, and behaves like a shell pipe.  Thanks again for the instructive example program.  The man program uses a library, libpipeline, that allows to do more complex stuff such as constructing a pipe (like the shell) and feeding output to multiple programs.

Last edited by deepsoul (2013-03-14 12:10:10)


Officer, I had to drive home - I was way too drunk to teleport!

Offline

Board footer

Powered by FluxBB