delta
delta

Reputation: 3818

how to make ncurses program working with other linux utils?

Suppose I have a ncurses program which does some job on curses screen, and finally print something to stdout. Call this program c.c, compiled to a.out.

I expect cat $(./a.out) first fire up ncurses, after some action, a.out quits and print c.c to stdout, which is read by cat, and thus print content of file c.c.

#include <stdio.h>
#include <ncurses.h>

int main() {
    initscr();
    noecho();
    cbreak();
    printw("hello world");
    refresh();
    getch();
    endwin();
    fprintf(stdout, "c.c");
    return 0;
}

I also expect ./a.out | xargs vim, ls | ./a.out | xargs less to work.

But when I type ./a.out | xargs vim, hello world never shows up. The command seems not executed in order, vim does not open c.c.

What is the correct way to make a ncurses program to work with other linux utils?

Upvotes: 1

Views: 745

Answers (2)

that other guy
that other guy

Reputation: 123410

ncurses works by writing a bunch of ansi escapes to stdout, which the terminal will interpret. You can run ./a.out > file and then inspect the file to see what you're actually writing. It'll be immediately obvious why programs are confused:

$ cat -vE file
^[(B^[)0^[[?1049h^[[1;24r^[[m^O^[[4l^[[H^[[Jhello world^[[24;1H^[[?1049l^M^[[?1l^[>c.c

The correct way of doing this is to skip all the graphical/textual UI parts when you detect that stdout is not a terminal, i.e. it's consumed by a program instead of a user:

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

int main() {
  if(isatty(1)) {
    // Output is a terminal. Show stuff to the user.
    initscr();
    noecho();
    cbreak();
    printw("hello world");
    refresh();
    getch();
    endwin();
  } else {
    // Output is consumed by a program.
    // Skip UI.
  }
  fprintf(stdout, "c.c");
  return 0;
}

This is the canonical Unix behavior.

If you instead want to force your UI to be shown regardless, you can draw your UI on stderr.

Upvotes: 1

Thomas Dickey
Thomas Dickey

Reputation: 54475

Pipes use the standard output (stdout) and standard input (stdin).

The simplest way - rather than using initscr, which initializes the output to use the standard output, use newterm, which allows you to choose the file descriptors, e.g.,

newterm(NULL, stderr, stdin);

rather than

initscr();

which is (almost) the same as

newterm(NULL, stdout, stdin);

By the way, when you include <ncurses.h> (or <curses.h>), there is no need to include <stdio.h>.

If you wanted to use your program in the middle of a pipe, that is more complicated: you would have to drain the standard input and open the actual terminal device. But that's another question (and has already been answered).

Further reading:

Upvotes: 2

Related Questions