0

My output gets corrupted when I try to pipe some output to another command in the shell. This doesn't happen when I execute the executable alone, or redirect its output to a program that doesn't care.

My executable

A simple program to print the size of the terminal window in Linux:

/* winsize.cpp to print terminal window size */
#include <iostream>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
    int rows = w.ws_row;
    int cols = w.ws_col;
    printf("%d lines, %d columns\n    %dx%d\n", rows, cols, cols, rows);
    printf("%d\n%d\n", cols, rows);
    return 0;
}

I know I used printf(). I'm getting there.

I compile it, and then run it with its relative path ./winsize or with an alias to its absolute path winsize.

$ winsize
56 lines, 213 columns
    213x56
213
56
$

This is the correct output.

Piping output

Now, I want to do something with the output, but the expected output never comes when I use another program with it.

Line isolation

$ winsize | sed '3q;d'
# expected output:
# 213
29058
$

An incorrect number is given. This number is random each time but appears to be restricted to integers in [0, 65535].

Pipe to self

$ winsize | winsize
# expected output is simply the correct output (because stdin is not read from)
56 lines, 213 columns
    213x56
213
56
$

This time the output is correct because this executable is not reading from stdin.

Piping to an a priori program that does read from stdin

Perhaps it was a problem with Linux programs. I wrote my own to test this. The program simply reads each byte from stdin and write the ones read so far to stdout upon reading a newline, including the newline. The program will halt when EOF is read from stdin.

Testing the compiled program with piping from a Linux program:

$ echo "hello" | ./test_echo
# expected output:
# hello
hello
$

This new program works correctly with an arbitrary line print.

Now, giving this program my executable:

$ winsize | ./test_echo
# expected output is simply the correct output
28864 lines, 24898 columns
    24898x28864
24898
28864
$

It is incorrect, again, and with 28864 again.

Process substitution into an a priori program that does read from stdin

The same new program is used.

$ test_echo < <(winsize)
# expected output is simply the correct output
28864 lines, 7778 columns
    7778x28864
7778
28864
$

Even with this method, an incorrect answer is produced.

Piping to an a priori program that does not read from stdin

$ winsize | ./isprime 15965253440080127741
# expected output:
# 1
1
$

This newer program's output is correct, because there was no read from stdin, I assume.

Problem

The correct information appears if and only if I am running the executable with no other commands/executables that read the output of the program (whether by ./winsize or winsize), discounting coincidence. Neither of these will ever produce output that is incorrect, and running the executable with something else will always cause incorrect output.

All other programs pipe correctly. I have no issues with things like ls / | tac.

28864 for the number of lines seems to be a constant. The number of columns is random and fluctuates every time output is generated.

But, if I change <iostream> to <stdio.h> and compile it as C++ with g++, I get the following.

$ winsize | cat
18425 lines, 48255 columns
    48255x18425
48255
18425
$

Now both are randomly fluctuating.

Now, if I compile it as C with gcc, I get the following.

$ winsize | cat
4096 lines, 0 columns
    0x4096
0
4096
$

It's always 4096 lines, 0 columns. Every time. This is constant.

And then, if I replace the printf() lines with std::cout instead, while also restoring the directive to include <iostream>:

    std::cout << rows << " lines, " << cols << " columns\n";
    std::cout << "    " << cols << "x" << rows << "\n";
    std::cout << cols << "\n" << rows << "\n";

And compile it as C++ with g++, I get the following.

$ winsize | cat
28296 lines, 4546 columns
    4546x28296
4546
28296
$

The 28296 is constant, and the number of columns reported is random.

I have no idea what's going on.

The first and second lines of the output are for quick human parsing, but the third and fourth lines are helpful for passing the information along to other programs. Unfortunately, the program thinks it's Superman and would rather work alone.

1 Answers1

1
ioctl(STDOUT_FILENO

When you execute ./yourprog | cat then stdout is connected to cat. cat is no terminal. It has no size. No cursor position. It's a program. It reads input. In other words, isatty(STDOUT_FILENO) is false.

If you want to get the size of the terminal, use a file descriptor connected to the terminal. You could open /dev/tty or use still connected to terminual STDERR_FILENO, or open a different number file descriptor in bash to an terminal and use it in your program, etc.

This time the output is correct because this executable is not reading from stdin.

The output from the left-side program is not connected to the terminal so "wrong" in your sense and ignored. The output from the right-side program is visible and references open terminal. It has nothing to do with "not reading from stdin" - any of the programs may have read input, and it wouldn't affect the outcome.

correct wrong

The a program output doesn't match your expectation doesn't mean it is wrong. You did not check the return value of ioctl in your programs. When ioctl fails becuase the file descriptor is not connected to terminal, returns -1 and errno is set to ENOTTY and the variable struct winsize w is uninitialized, so the program outputs some garbage value. This program output is "correct", the program works as intended. The output of C and C++ programs "happens" to be different because they "happen" to leave different values in the uninitialized memory taken by variable w.

Remember to do error checking.

4096 lines, 0 columns. Every time. This is constant.

Note that "garbage value" doesn't mean "random". My favorite quote: "If you throw an apple in the garbage, it's still an apple".

Process substitution

The output of program run inside a process substitution is connected to a temporary fifo. Not a terminal.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111