我感兴趣的是如何使用 bash 内置控制结构和函数在技术上实现重定向。
例如,我有以下命令
while read line; do echo $line; done < lines.txt | tac > ~/reversed.txt
什么机制将标准输入(lines.txt)连接到read
命令( 的参数while
)以及什么将do
主体连接到标准输出(管道)?显然应用了一些上下文规则(与外部命令重定向相反),但这些规则到底是什么以及 bash 在技术上如何实现它们?
答案1
通常,UNIX shell 只需使用 来打开所需的文件open
,然后fork
打开它们本身,然后将dup2
之前获得的 fd 发送到 stdin/stdout/stderr (0/1/2),以便execve
稍后的程序对它们进行相应的处理。为了提高性能,它可能与内置命令不同(因为fork
和execve
非常昂贵),但语义是相同的。
不过,如果您指的是命令行解析规则,则 POSIX 中对此进行了描述。他们不区分内置程序和外部程序。
答案2
重定向文件描述符d0传入或传出文件涉及以下步骤:
- 打开文件。文件在某个文件描述符上打开d1。
- 复制描述符d0到当前未使用的文件描述符d2它大于d0。这可以通过
F_DUPFD
以下命令来完成fcntl
系统调用。如果d0未打开则此步骤不执行任何操作。 - 复制d1到d0。这可以通过
F_DUPFD
或 来完成dup2
。 - 关闭d1。
需要重复洗牌的原因是应用程序在打开文件时无法选择文件描述符。如果满足以下条件,则可以省略步骤 2-4:d1=d0但shell不能保证这一点。
当重定向应用于外部命令时,它会在子进程创建后在子进程中执行fork
但在执行外部命令之前execve
。当重定向应用于内部 shell 命令(例如函数调用、循环等)时,这些步骤必须在原始进程中执行,并且 shell 需要在重定向命令完成后恢复原始文件描述符状态,通过复制d2回到d0并关闭d2(或者只是关闭d0如果最初没有开放)。
管道涉及类似的步骤,但要复杂一些,因为创建管道会创建两个文件描述符(读端和写端),并且有两个子进程。
创建一个管道
pipe
。系统pipe
调用返回一对文件描述符{r,w}。在管道的左侧:
- 关闭r。
- 进行复制随机移动w至 1。
在管道的右侧:
- 关闭w。
- 进行复制随机移动r至 0。
在子进程中执行管道两侧的 shell 中,父进程关闭r和w,然后等待管道两侧终止。
在父进程中执行管道右侧的 shell 中,shell 等待左侧终止,然后关闭 0 并在 0 处恢复原始文件描述符。
您可以通过阅读 shell 的源代码或使用调试器跟踪它们的运行来了解 shell 的功能。例如,在 Linux 上,查看正在运行的系统调用
strace sh -c '…'
1古代 shell(POSIX 之前)在子进程中执行重定向的复杂命令,因此它们不需要任何重定向恢复。