Bash 内置控制结构/命令重定向如何工作

Bash 内置控制结构/命令重定向如何工作

我感兴趣的是如何使用 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稍后的程序对它们进行相应的处理。为了提高性能,它可能与内置命令不同(因为forkexecve非常昂贵),但语义是相同的。

不过,如果您指的是命令行解析规则,则 POSIX 中对此进行了描述。他们不区分内置程序和外部程序。

答案2

重定向文件描述符d0传入或传出文件涉及以下步骤:

  1. 打开文件。文件在某个文件描述符上打开d1
  2. 复制描述符d0到当前未使用的文件描述符d2它大于d0。这可以通过F_DUPFD以下命令来完成fcntl系统调用。如果d0未打开则此步骤不执行任何操作。
  3. 复制d1d0。这可以通过F_DUPFD或 来完成dup2
  4. 关闭d1

需要重复洗牌的原因是应用程序在打开文件时无法选择文件描述符。如果满足以下条件,则可以省略步骤 2-4:d1=d0但shell不能保证这一点。

当重定向应用于外部命令时,它会在子进程创建后在子进程中执行fork但在执行外部命令之前execve。当重定向应用于内部 shell 命令(例如函数调用、循环等)时,这些步骤必须在原始进程中执行,并且 shell 需要在重定向命令完成后恢复原始文件描述符状态,通过复制d2回到d0并关闭d2(或者只是关闭d0如果最初没有开放)。

管道涉及类似的步骤,但要复杂一些,因为创建管道会创建两个文件描述符(读端和写端),并且有两个子进程。

  1. 创建一个管道pipe。系统pipe调用返回一对文件描述符{r,w}。

  2. 在管道的左侧:

    1. 关闭r
    2. 进行复制随机移动w至 1。
  3. 在管道的右侧:

    1. 关闭w
    2. 进行复制随机移动r至 0。
  4. 在子进程中执行管道两侧的 shell 中,父进程关闭rw,然后等待管道两侧终止。

    在父进程中执行管道右侧的 shell 中,shell 等待左侧终止,然后关闭 0 并在 0 处恢复原始文件描述符。

您可以通过阅读 shell 的源代码或使用调试器跟踪它们的运行来了解 shell 的功能。例如,在 Linux 上,查看正在运行的系统调用

strace sh -c '…'

1古代 shell(POSIX 之前)在子进程中执行重定向的复杂命令,因此它们不需要任何重定向恢复。

相关内容