为什么我的输出在通过管道传输到从标准输入获取输入的程序后不能保持完整性?

问题描述 投票:0回答:1

当我尝试将某些输出传输到 shell 中的另一个命令时,我的输出被损坏。当我单独执行可执行文件或将其输出重定向到不关心的程序时,不会发生这种情况。

我的可执行文件

在 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;
}

我知道我用过

printf()
。我快到了。

我编译它,然后使用它的相对路径运行它:

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

输出正确。

现在,我在

~/.bashrc
中为绝对路径起别名:

alias winsize='/path/to/file/winsize'

然后我运行它:

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

这也是正确的。

管道输出

现在,我想对输出做一些事情,但是当我使用另一个程序时,预期的输出永远不会出现。

线路隔离

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

给出的数字不正确。这个数字每次都是随机的,但似乎仅限于 [0, 65535] 中的整数。

模式匹配

$ winsize | grep 213
# expected output:
# 213
$

没有给出输出,因为如果这个数字是随机的,那么它就不太可能是正确的数字。

二进制输出

$ winsize | hexdump
# expected output: 
# 0000000 3635 6c20 6e69 7365 202c 3132 2033 6f63
# 0000010 756c 6e6d 0a73 2020 2020 3132 7833 3635
# 0000020 320a 3331 350a 0a36 000a               
# 0000029
0000000 3832 3638 2034 696c 656e 2c73 3120 3439
0000010 3835 6320 6c6f 6d75 736e 200a 2020 3120
0000020 3439 3835 3278 3838 3436 310a 3439 3835
0000030 320a 3838 3436 000a                    
0000037
$

错误的字节流被打印到标准输出。

连接到标准输出

$ winsize | cat
# expected output:
# 56 lines, 213 columns
#     213x56
# 213
# 56
28864 lines, 45890 columns
    45890x28864
45890
28864
$

检索到错误答案。

重定向到文件

$ winsize > /tmp/a.txt
$ cat /tmp/a.txt
# expected output is identical to above example
28864 lines, 14178 columns
    14178x28864
14178
28864
$

该文件不包含正确的信息。

有条件继承

$ winsize && winsize
# expected output is simply the correct output doubled
56 lines, 213 columns
    213x56
213
56
56 lines, 213 columns
    213x56
213
56
$

这次输出正确了。

管道到自身

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

再次,这次输出是正确的,因为此可执行文件不是从 stdin 读取。

通过管道传输到先验程序,该程序确实从标准输入读取

可能是Linux程序的问题。我自己写了一个来测试这个。

创建了以下程序:

/* test_echo.cpp to parrot what stdin said to stdout */
#include <iostream>

int main() {
    char c;
    while (std::cin.read(&c, 1)) {
        std::cout << c;
    }
    return 0;
}

程序将从 stdin 读取每个字节,并在读取换行符(包括换行符)时将迄今为止读取的字节写入 stdout。当从 stdin 读取 EOF 时,程序将停止。

使用 Linux 程序中的管道测试编译后的程序:

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

这个新程序可以正确地打印任意行。

现在,给这个程序我的可执行文件:

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

又错了,又是28864。

将替换过程替换为先验程序,该程序从标准输入

读取

使用相同的新程序。

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

即使使用这种方法,也会产生错误的答案。

通过管道传输到先验程序,该程序从标准输入

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

这个新程序的输出是正确的,因为我认为没有从标准输入读取数据。

问题

当且仅当我运行可执行文件而没有其他读取程序输出的命令/可执行文件(无论是通过

./winsize
winsize
)时,才会出现正确的信息,不考虑巧合。这些都不会产生不正确的输出,并且用其他东西运行可执行文件总是会导致不正确的输出。

所有其他程序都可以正确进行管道传输。我对

ls / | tac
之类的事情没有任何问题。

28864 的行数似乎是一个常数。列数是随机的,并且每次生成输出时都会波动。

但是,如果我将

<iostream>
更改为
<stdio.h>
并使用 g++ 将其编译为 C++,我会得到以下结果。

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

现在两者都随机波动。

现在,如果我用 gcc 将其编译为 C,我会得到以下结果。

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

始终是 4096 行,0 列。每次。这是恒定的。

然后,如果我将

printf()
行替换为
std::cout
,同时还恢复指令以包含
<iostream>

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

并用 g++ 将其编译为 C++,我得到以下结果。

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

28296是常数,报告的列数是随机的。

我不知道发生了什么。

输出的第一行和第二行用于快速人工解析,但第三行和第四行有助于将信息传递给其他程序。不幸的是,该程序认为它是超人,宁愿单独工作。

c++ linux pipe stdout stdin
1个回答
0
投票
ioctl(STDOUT_FILENO

当您执行

./yourprog | cat
时,标准输出将连接到
cat
cat
不是终结符。它没有尺寸。无光标位置。这是一个程序。它读取输入。换句话说,
isatty(STDOUT_FILENO)
是假的。

如果要获取终端的大小,请使用连接到终端的文件描述符。您可以打开 /dev/tty 或使用仍然连接到终端的 STDERR_FILENO,或者在 bash 中打开不同编号的文件描述符到终端并在您的程序中使用它,等等。

© www.soinside.com 2019 - 2024. All rights reserved.