在 Linux 中使用系统调用 READ 读取 STDIN:未使用的输入被发送到 bash

Read STDIN using syscall READ in Linux: unconsumed input is sent to bash

以下程序(64 位 YASM)从标准输入读取 4 个字节并退出:

    section .data
buf   db "               "      ; Just allocate 16 bytes for string
    section .text
    global _start
_start:
    mov rax, 0                  ; READ syscall
    mov rdi, 0                  ; STDIN
    mov rsi, buf                ; Address of the string
    mov rdx, 4                  ; How many bytes to read
    syscall
    ; Exit:
    mov rax, 60
    mov rdi, 0
    syscall

一次编译

yasm -f elf64 -l hello.lst -o input.o input.asm
ld -o input input.o

如果是运行就好

./input

,比如说,123456\n 作为用户输入,它将消耗 1234,但结束位 56\n 被发送到 bash。因此,bash 将尝试 运行 命令 56...谢天谢地没有成功。但是想象一下如果输入是 1234rm -f *。但是,如果我使用重定向或管道提供输入,例如,

echo "123456" | ./input

56 没有发送到 bash.

那么,如何防止将未使用的输入发送到 bash?在遇到某种形式的 EOF 之前,我是否需要继续使用它?这甚至是预期的行为吗?

同样的事情发生在 C 程序中:

#include <unistd.h>

int main()
{
    char buf[16];
    read(0, buf, 4);
    return 0;
}

(我只是想知道 C 运行time 是否以某种方式清除了 STDIN,但没有,它没有)

是的,这是正常行为。您不消耗的任何东西都可用于下一个过程。你知道当你做一些慢的事情时你可以提前输入,当慢的事情完成时 shell 会 运行 你输入的内容吗?这里也是一样。

没有放之四海而皆准的解决方案。这实际上与用户期望有关。他们希望您的程序消耗多少输入?这就是你应该阅读的内容。

  • 你的程序是不是像read一样是单行提示?然后你应该阅读下一个 \n 字符的完整输入行。在不过度阅读的情况下做到这一点的最简单方法是一次阅读 1 个字符。如果您进行批量读取,您可能会误读下一行的一部分。

  • 您的程序是否像 catsedgrep 这样的过滤器?然后你应该一直读到 EOF。

  • 您的程序是否完全不像 echogcc 从 stdin 读取?那么你应该让 stdin 保持独立,不消耗任何东西,将输入留给下一个程序。

恰好使用 4 个字节是不寻常的,但对于提示输入 4 位 PIN 且不需要用户按 Enter[=38= 的交互式程序来说可能是合理的行为].

"Is sent to" bash(或任何其他程序)是一种次优的思考方式,也许这会导致您感到惊讶/困惑。 "Made available to" 将是更准确的表征,无论您是在谈论终端、管道还是连接到程序标准输入的任何其他输入源。

当一个进程派生另一个进程时,例如 shell 执行您输入的许多命令,新进程会从其父进程继承很多属性。其中包括其打开的文件描述符,尤其是父级标准流的文件描述符。在 POSIX 系统上,这是在没有重定向的情况下进程的标准流的来源,也是实现重定向的机制。

因此,当不涉及 I/O 重定向时, 当然 父 shell 读取其启动的程序未读的输入数据。如果父 shell 也无法使用输入,则这些程序无法使用输入,因为它们都从同一来源读取。这也是为什么你可以在同一个终端中在前台和后台之间移动程序,并且每个程序在前台时都可以从终端读取。

鉴于您特别提到了 read() 系统调用,我怀疑您的惊讶也可能与使用 stdio 函数从标准输入读取的程序看到不同的行为有关。这与以下事实有关:当标准输入连接到终端时,stdio 函数默认以行缓冲模式读取它。也就是说,它们将数据从底层源传输到内部缓冲区,从而将其从流中删除,一次一行(有一些注意事项)。

您可以使用 read 来模拟它。最简单的方法是一次读取一个字节,直到看到换行符或文件结尾,但是 C 库函数通过在标准输入实际上连接到终端。你也可以直接这样做,但是有点复杂。