输入重定向如何工作?

输入重定向如何工作?

据我了解,任何从标准输入(即键盘)读取的命令都会从文件中获取输入。

$echo < text_content.txt

$

但是 echo 命令没有读取并在终端上显示 text_content.txt。这里有什么问题?

答案1

标准输入和命令

据我了解,任何从标准输入(即键盘)读取的命令都会从文件中获取输入。

是的。正如我在Linux/Unix 中文件的特征是什么?,文件是任何可以对其执行标准操作(例如read()open()write()、 )的对象close()stdin通过文件描述符 0 表示的文件实际上就是文件,并且 Linux 中的任何命令/进程在启动时都会获得 3 个标准文件描述符 - stdin、stdout、stderr。这些文件描述符背后的实际文件是什么?命令不关心也不应该关心,只要它可以对其执行操作即可。

但是 echo 命令没有读取并在终端上显示 text_content.txt。这里有什么问题?

现在,命令可以自由地对这些文件描述符1执行其想要的操作。如果是,echo它仅处理stdout而不执行任何操作stdin。因此命令本身没有任何问题。

<重定向将读取open()文件text_content.txt,并且它仍将分配从open()调用返回的文件描述符(例如 3)到文件描述符 0,并且如果命令与 stdin 有关 - 它将像什么都没发生一样从文件描述符 0 读取。事实上,如果你运行strace -f -e dup2,write,openat bash -c 'echo < text_content.txt

openat(AT_FDCWD, "/etc/passwd", O_RDONLY) = 3
dup2(3, 0)                              = 0
write(1, "\n", 1
)                       = 1
dup2(10, 0)                             = 0
+++ exited with 0 +++

注意dup2()系统调用。这就是文件描述符 3(文件)的分配/重定向方式。按照某种cp original copy语法,dup2(3,0)将文件描述符 3 复制到文件描述符 0,它们指向同一个文件。

还要注意,write()将换行符输出到文件描述符1。这是默认行为。如果我们这样做,strace -f -e dup2,write,openat bash -c 'echo FOO < /etc/passwd'我们将看到以下内容

dup2(3, 0)                              = 0
write(1, "FOO\n", 4FOO
)                    = 4
dup2(10, 0)                             = 0
+++ exited with 0 +++

所以再说一次,这里没有什么错误 - 重定向正确执行,并完成了将内容写入文件描述符 1 的echo工作。stdout


如何实际读取文件

现在,让我们讨论其他问题。我们如何在 shell 中读取文件?好吧,为此存在cat一个接受参数的命令,因此您只需执行即可cat file.txt。你能做到吗cat < file.txt?当然。但这意味着 shell 必须执行该dup2()调用,而 则cat file.txt不需要 - 因此我的意思是说,不必要的系统调用更少。

在复杂的情况下,例如当你需要对文件的每一行执行操作时,你可以这样做

while IFS= read -r line || [ -n "$line" ]; do
    # command to process line variable here
done < /etc/passwd

现在,对于整个循环,文件描述符0将是打开时返回的任何文件描述符的副本/etc/passwd。当然,如果你可以使用cat或其他特定命令来读取文件 - 那么就这样做吧。Shell 是一种缓慢的方法,并且有很多缺陷。另请参阅,为什么使用 shell 循环来处理文本被认为是不好的做法?


1. 一些应用程序可能仍然关心它们可以用 stdin 做什么,或者检测 stdin 是文件还是管道。当stdin文件描述符被指定为管道的读取端(也是文件描述符)时,输出是不可查找的(这意味着用 C 或其他语言编写的应用程序无法使用seek()syscall 快速导航到文件中的特定字节偏移量)。标题为“cat file | ./binary” 和 “./binary < file” 有什么区别?

旁注:在 Linux 上,stdin 并不是从键盘获取输入的。如果你这样做

$ ls -l /proc/self/fd/0
lrwx------ 1 serg serg 64 Feb 23 16:45 /proc/self/fd/0 -> /dev/pts/0

您将在输出中看到它最初指向的是/dev/pts/0终端设备stdin。然后终端设备与键盘接口,或者它也可能是串行电缆。

此外,如果文件不是很大,您可以利用bash内置mapfile函数将行读入数组:

mapfile  -t  < /etc/passwd
for i in "${MAPFILE[@]}"; do echo "$i"; done

相关内容