例如,bash位于/bin/bash下,这意味着它是一个命令,每个命令都有三个(0,1,2)孔:标准输入,标准输出,标准错误。
这对于 shell 来说也是 100% 正确吗?还是因为 shell 作为命令或进程的特殊含义而有什么不同?
答案1
它与任何其他程序相同。这允许您像其他程序一样重定向和管道 I/O。
echo "cat filename" | bash
当从管道读取其标准输入时将执行该cat filename
命令。bash
bash -c "echo foo" > filename
将执行该echo foo
命令,并将输出重定向到该文件。
在 Unix 上,shell 没有什么“特别”的地方。它只是一个普通的程序,其主要目的是执行其他程序。
答案2
让我们区分一些术语:
A命令是你在 shell 中输入的内容。它可能是一个别名或一个外壳函数,或者它可能指的是可执行文件。
一个可执行文件可能是一个二进制可执行文件(即直接包含机器代码的代码)或脚本。脚本包括bash脚本、sh脚本、perl脚本、awk脚本、sed脚本、python脚本等。等人。
脚本的前两个字节(如果要直接作为可执行文件执行)必须是#!
,这是一个“幻数”,向内核发出信号以读取更多字节,直到读取换行符,将这些字节解释为到一个的路径二进制可执行文件,可能有一个由空格分隔的参数(例如#!/bin/awk -f
),然后执行那二进制可执行文件,脚本本身的路径作为参数传入。
最终,内核唯一可以真正执行的是机器代码,即二进制可执行文件。二进制可执行文件,例如 sh、bash、perl、python、awk 等。等人。叫做口译员。 他们解释脚本并执行其指令。但它们必须存在于机器代码本身中才能被执行。
当程序(二进制可执行文件)实际由内核运行时,它作为过程。 二进制可执行文件只是一个包含指令的文件。 A过程是“程序的运行实例”;更具体地说,它是内置于内核中的一个抽象,具有关联的内存、环境变量、进程 ID (PID)、文件描述符它可用于输入和输出以及其他属性。您可以“同时”多次运行一个可执行文件(实际上并不是在单核机器上同时运行,而是由于内核分配 CPU 周期的方式,它会似乎同时)并且每个正在运行的实例将是不同的进程,即使它们都是同一程序的实例。
0、1 和 2(标准输入、标准输出和标准错误)是文件描述符。 说实话,它们的存在只是按照惯例。您可以使用 C 创建一个程序,该程序将启动(运行)其他程序(执行各种二进制可执行文件),而无需向它们提供这些文件描述符。 然而,由于标准程序都是在假设文件描述符 0、1 和 2 可用的情况下编写的,因此您可能只会得到错误(在大多数情况下),并且程序将无法正常工作。
要真正完全理解这一点,您应该理解一个过程是如何产生的。 这有点像诞生的奇迹。 ;) 所有进程都必须由其他过程。不用担心启动系统时第一个进程如何启动,PID 为 1 的进程称为“init”,它会启动操作系统运行所需的其他基本进程。
一个进程如何启动另一个进程有两个基本步骤:叉和执行。 这两个都是系统调用,也就是说它们是进程发送的操作/请求到内核只有内核才能真正实现。
“Fork”的意思是(简而言之)“内核,请给我复制一份”。 (“Me”是一个正在运行的进程。)内核制作进程的完整副本——它的文件描述符、内存、执行状态(它在遵循组成它是实例的程序的指令时所处的位置),环境变量等等。所以它是该过程的“克隆”。现在你如何区分原件和副本呢?仅通过一件事:系统调用的返回状态fork
。子进程获取“0”(成功),父进程获取新创建的子进程的PID。因此,通过检查此返回状态,每个进程都可以弄清楚它现在应该做什么(因为记住,它们遵循同一组指令!)。
“Exec”实际上是“execve()”。简而言之,它询问内核:“内核,请代替我(我是一个进程),具有 ______ 文件中指定的程序实例。”并且程序员还指定了论点新流程将具有的,以及环境它将拥有的(环境变量数组)。
因此,当你在 shell 中输入命令时,实际发生的情况(大多数情况下,忽略诸如 shell 内置命令之类的特殊情况cd
)是你的 shell(一个正在运行的进程)叉子,进而执行官您指定的命令。
如果您已经完成了输出或输入重定向/bin/echo hello > /dev/null
,那么分叉的子进程exec
在 ing echo之前将相应地调整其文件描述符,以便文件描述符 1(在本例中)被绑定到/dev/null
而不是您的终端或之前的任何位置。
所以是的,可执行文件的任何正在运行的实例/bin/bash
都期望有一个可用的文件描述符 0(可以从中读取输入)、一个文件描述符 1(可以写入输出)以及一个文件描述符 2(可以读取和写入错误)消息和类似的输入/输出。