假设 Linux 中有两个文件。FileA和FileB都包含一些不同的水果列表。我应用以下命令。
diff fileA fileB > file.diff
接下来,我执行以下命令
patch fileA 0< file.diff
上述命令根据file.diff给出的输入修补(更正)原始文件(fileA ) ,并将输出发送到fileA(这是我的理解,我可能错了)。换句话说,fileA和fileB匹配。
据我所知,“0<” 是标准输入的重定向符号。现在,由于标准输入是键盘,patch 命令不应该从键盘读取而不是从file.diff读取吗?上述命令如何工作?
该答案是使用 Bash shell 测试的。
以下命令相同。
1
是默认值,因此可以省略。 基本上, 的标准输出diff
从屏幕重定向到文件file.diff
。以下命令相同。
0
是默认值,因此可以省略。基本上,标准输入patch
从键盘重定向到文件file.diff
。我将尝试通过以下内容进行解释。当我输入命令时
tty
,我得到以下输出。这意味着终端窗口已被分配文件名
/dev/pts/0
。标准输入和标准输出分别被分配文件名/dev/fd/0
和/dev/fd/1
。下面的命令测试标准输入 (
/dev/fd/0
) 和终端窗口 (/dev/pts/0
) 是否具有相同的设备和 inode 值。换句话说,测试它们是否相同。在本例中,输出为true
。以下命令测试标准输入 (
/dev/fd/0
) 和文件是否file.diff
具有相同的设备和 inode 值。在这种情况下,输出为false
。但是,如果标准输入被重定向为来自文件
file.diff
,如下所示,那么输出它true
。到目前为止,我的回答已经解释了使用 Bash shell 时重定向的行为。此行为在所有操作系统上都应该一致。我避免了实现细节,因为这在不同的操作系统上可能会有所不同。您可能对一些实现细节感兴趣,因此我针对 Ubuntu Linux 介绍了以下内容。
下面的输出显示标准输入(
/dev/fd/0
)和标准输出(/dev/fd/1
)是终端窗口(/dev/pts/0
)的符号链接。以下是将标准输入重定向到文件 时的结果
file.diff
。现在,标准输入 (/dev/fd/0
) 已更改为文件 的符号链接file.diff
。不是。标准输入连接到键盘(或者更准确地说,连接到操作系统提供键盘输入的“tty”设备)。在任何时候,都可以关闭连接并在其位置打开其他东西 - 术语“标准输入”指的是特定的连接“槽”,而不是它去往的地方。(这就是为什么它被称为“标准输入”而不是“键盘输入”。)
这些数字不仅仅是 shell 语法;它们表示程序本身如何处理打开的文件。在每个进程中,每个“打开的文件”都由一个数字(文件描述符,或 Windows 所称的句柄)表示,并且所有读/写调用都基于该数字。按照标准惯例,无论哪个打开的文件被分配了文件描述符,
0
它都是“标准输入”。如果您从终端启动程序,则终端的“tty”将预先打开为 FD
0
- 或者更确切地说,从已经打开的 shell继承1
- 因此是程序的标准输入。(标准输出和2
标准错误也是如此。)然后“diff”程序本身将需要打开一些文件,因此它将调用open("fileA", ...)
,并且 fileA 将作为 FD 打开3
,依此类推。但是就像程序可以关闭它自己打开的任何文件一样(例如,它可以通过执行来关闭文件A
close(3)
),它也可以执行close(0)
来关闭其标准输入并在其位置打开其他文件;只要新打开的文件接收文件描述符,0
根据定义它就是“标准输入”。shell 可以在启动程序之前执行相同的操作。使用
<file.diff
或0<file.diff
意味着 shell 将关闭其原始 stdin 文件描述符,close(0)
并将文件open("file.diff")
作为新 FD打开0
,然后该文件将成为新的 stdin,由“diff”程序继承。(这发生在 shell 为启动“diff”而创建的子进程中,而不会影响主 shell 进程。)现在,当 diff 调用时,read(0, …)
它将从文件中读取。附注:
通常
open()
使用最低可用的文件描述符,例如,如果 0 刚刚关闭,则下一个打开的文件将再次为 0。如果需要更精确的控制,dup2()
可用于选择特定的 FD。可以重定向任何文件描述符,例如,
5>file.txt
将为程序提供一个5
与该文件相对应的预打开的 FD,但这仅在程序期望接收一个 FD 时才有用;否则它将保持打开状态但未被使用。(某些程序有可用于传递其他 FD 的选项gpg --status-fd=
。)Windows 具有类似的概念,其 cmd.exe 甚至具有相同的
2>
语法,但 Windows 文件句柄实际上不是从 0 开始编号的(它们是内存指针),因此 cmd.exe 仅实现 0/1/2 并将它们转换为 Windows 样式的标准句柄。(而 PowerShell 则是它自己的奇怪世界。)与 David Anderson 给出的示例类似,
ls -l /proc/self/fd
仅在 Linux 上,可用于检查其自己的文件描述符,或ls -l /proc/<pid>/fd
用于任何其他进程。例如:这里文件描述符 0 (stdin)、1 (stdout)、2 (stderr) 当前连接到终端(全部从 shell 继承),而 3 由 'ls' 本身打开,当然是列出的目录。如果使用 重定向 stdin
<
,则会得到:给程序提供了几个无用的文件描述符也是一样:
在 Linux 和其他类 Unix 操作系统中
当 shell 想要运行另一个程序时,它会通过调用 来克隆自己的副本
fork()
(这看起来似乎很繁重,但通过写时复制的魔力,工作量和资源成本大大降低)。然后,该副本通过调用 将其自身替换为想要运行的程序exec()
(实际的 OS 调用是execve()
)在需要输入或输出重定向的情况下,分叉的副本知道这个,并且这些文件或流在
fork()
和之间打开exec()
,因此原始 shell 保留与原始 I/O 设备的连接,但启动新程序的环境已经改变。这在LPG中有详细解释
“重定向”这个术语可能有点令人困惑,重定向的是进程的读取调用,因为读取文件描述符(如 STDIN)是拉取而不是推送。进程从“STDIN”拉取数据。
通常,您有一个连接,将 STDIN 连接到您的键盘。您在键盘上输入的所有内容都将保存在缓冲区中,当进程从“STDIN”读取时,它将从该缓冲区读取并获取您输入的键。
Process --read-> STDIN --read-> Keyboard-Buffer
当您使用此命令启动进程时,
proc 0< file.diff
会将进程的读取调用重定向到 file.diff 的 STDIN。现在有一个从 STDIN 到 file.diff 的连接,而不是键盘。Process --read-> STDIN --read-> file.diff
该过程仍然可以通过访问类似的内容(取决于终端)直接从键盘缓冲区读取
/dev/pts/0
,但是读取 STDIN 的调用现在将从 file.diff 读取。