0

I have simple program that reads file line by line, and prints each line. The core of the program is:

while ((size = getline(&line, &len, f)) != -1)
    printf("%s", line);

Now I have following problem:

this works:

./test zz

this also works:

/bin/cat <(./test zz)

but when I pipe the output to other program, it does not work:

/bin/cat <(./test zz) | /bin/cat

there is no output. When I compare with strace the one that works, and the one that doesn't:

/bin/cat <(strace ./test zz)

/bin/cat <(strace ./test zz) | /bin/cat

I see this error: -1 ENOTTY (Inappropriate ioctl for device)

ioctl(0, TCGETS, 0x7ffc302104d0)        = -1 ENOTTY (Inappropriate ioctl for device)
brk(NULL)                               = 0x55b4e1fd3000
brk(0x55b4e1ff4000)                     = 0x55b4e1ff4000
fstat(0, {st_mode=S_IFCHR|0666, st_rdev=makedev(0x1, 0x3), ...}) = 0
ioctl(0, TCGETS, 0x7ffc30210350)        = -1 ENOTTY (Inappropriate ioctl for device)
read(0, "", 4096)                       = 0
close(0)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

EDIT:

here is my proof-of-concept code:

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


int main(int argc, char **argv) {

    FILE *f;
    char *line = NULL;
    size_t len = 0;
    ssize_t size, cut;


    if (!isatty(STDIN_FILENO) || argc < 2 || (argv[1] == "-"))
        f = stdin;
    else
        if (!(f = fopen(argv[1], "r"))) {
            perror(argv[1]);
            return 1;
        }

    while ((size = getline(&line, &len, f)) != -1)
    printf("%s", line);

    fflush(stdout);
    fclose(f);
    if (line)
        free(line);
    exit(EXIT_SUCCESS);
}

EDIT2:

I have removed the part: (!isatty(STDIN_FILENO) and that fixed the problem. But I don't understand what is happening.

I originally added this, so that my program can take filename as argument ./test zz or take input from pipe cat zz | ./test.

400 the Cat
  • 266
  • 3
  • 23
  • works for me - with fgets instead of your getLine – pm100 Mar 16 '23 at 17:45
  • 3
    The `ioctl` is the implementation of the `isatty` function, which either your code or the standard library presumably calls. The "error" return simply indicates that fd 0 is not a terminal, which is correct. So I don't think that's directly related to your problem. You should post a [mcve]. – Nate Eldredge Mar 16 '23 at 17:55
  • @Nate Eldredge thanks. I have added proof-of-concept code. Please see my edit. – 400 the Cat Mar 16 '23 at 17:58
  • 2
    Why are you doing this? `!isatty(STDIN_FILENO)` – Solomon Ucko Mar 16 '23 at 18:08
  • 1
    If you want each line to be written out to the pipe as soon as you print it, then you have to fflush immediately - i.e. *inside* the loop, not after it completes. Your program does work as it stands, in that all the input eventually gets echoed to the output - just not immediately after every line. – Nate Eldredge Mar 16 '23 at 18:10
  • @Nate Eldredge - I moved the flush inside the while loop, but it makes no difference. – 400 the Cat Mar 16 '23 at 18:12
  • @Solomon Ucko - please see my second edit – 400 the Cat Mar 16 '23 at 18:18
  • 3
    Aside: `argv[1] == "-"` does not do what you want. Use `strcmp` to compare strings. – Ian Abbott Mar 16 '23 at 18:24
  • It seems that standard input of the `./test` execution is not actually coming from a terminal. Are you running it from an IDE? – Ian Abbott Mar 16 '23 at 18:36
  • @Ian Abbott - no. I am in the terminal. – 400 the Cat Mar 16 '23 at 18:40
  • I guess it must be something to do with the shell then. – Ian Abbott Mar 16 '23 at 18:52
  • Just to confirm, does `[ -t 0 ] && echo terminal` output `terminal`? – Ian Abbott Mar 16 '23 at 18:53
  • 1
    @Ian Abbott - yes. all teh above was done in `zsh` and when I do as you say it returns `terminal`. But interestingly, when I use bash, instead of `zsh` the command works fine: `/bin/cat <(./test zz) | /bin/cat` – 400 the Cat Mar 16 '23 at 19:01
  • @Ian Abbott - could you please explain why I should use `strcmp` instead of`argv[1] == "-"`? It seems to work fine. – 400 the Cat Mar 16 '23 at 19:23
  • 3
    @400theCat, because the version with `==` doesn't mean what you think it means. See [How do I properly compare strings in C?](https://stackoverflow.com/q/8004237/2402272) – John Bollinger Mar 16 '23 at 19:35
  • 2
    OK, so it seems to something to do with the way Zsh supplies standard input for that command. I don't know why it would supply standard input for `./test` differently in `/bin/cat <(./test)` and `/bin/cat <(./test) | /bin/cat`. – Ian Abbott Mar 17 '23 at 11:27

2 Answers2

3

If any of the following conditions are true, the program uses stdin for input:

  • stdin isn't connected a terminal.
  • No arguments were provided.
  • The first argument is -.

In the scenario you presented, stdin isn't connected to a terminal[1], so the test reads from stdin. You don't get the desired result since you want to read from the file zz.

The first condition (!isatty(STDIN_FILENO)) makes no sense. You were correct to remove it. The remaining two conditions are sufficient to make cat zz | ./test work.


  1. I don't know why. But it's not important. There are plenty of times stdin isn't a terminal. For example, the stdin of daemons aren't terminals, and thus the stdin of program launched by deamons aren't terminals unless the daemon takes steps to make it so.
ikegami
  • 367,544
  • 15
  • 269
  • 518
  • 1
    this solutiuon was suggested to me when I asked few days ago: https://stackoverflow.com/questions/75730197/error-handling-when-opening-either-file-or-stdin how else should I decide whether my program is on the receiving side of a pipe? – 400 the Cat Mar 16 '23 at 18:58
  • 2
    But you don't need to know whether it's on the receiving end of a pipe. – ikegami Mar 16 '23 at 18:58
-3

The flush should be done to stdout, not the file you're reading.

Solomon Ucko
  • 5,724
  • 3
  • 24
  • 45