在 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 个字符。如果您进行批量读取,您可能会误读下一行的一部分。
您的程序是否像 cat
或 sed
或 grep
这样的过滤器?然后你应该一直读到 EOF。
您的程序是否完全不像 echo
或 gcc
从 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 库函数通过在标准输入实际上连接到终端。你也可以直接这样做,但是有点复杂。
以下程序(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 个字符。如果您进行批量读取,您可能会误读下一行的一部分。您的程序是否像
cat
或sed
或grep
这样的过滤器?然后你应该一直读到 EOF。您的程序是否完全不像
echo
或gcc
从 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 库函数通过在标准输入实际上连接到终端。你也可以直接这样做,但是有点复杂。