Bash 内置也作为单独的可执行文件提供



我想了解bash内置。因此,以下问题:

  1. 当我想到内置术语时,我认为bash可执行文件在其符号表中定义了一个函数,可执行文件的其他部分可以访问该函数而无需实际fork。这就是内置的意思吗?
  2. 我还看到一些内置的具有单独的可执行文件。例如,type [返回[ is a shell builtin.但是后来我也看到了一个名为/usr/bin/[的可执行文件。说相同的代码可以通过两个可执行文件获得是否正确:一个通过bash程序,另一个通过/usr/bin/[

bash 可执行文件在其符号表中定义了一个函数

Bash可执行文件中包含内置函数。您可以在运行时从单独的共享库动态加载内置。

无需实际分叉即可访问

是的。

说相同的代码可以通过两个可执行文件获得是否正确:一个通过 bash 可执行文件,另一个通过/usr/bin/[?

不,这是一个不同的源代码。一个是内置的Bash,另一个是程序。它将是一个不同的源代码。在灰色地带也有不同的行为。

$ printf "%qn" '*'
*
$ /bin/printf "%qn" '*'
'*'
$ time echo 1
1
real    0m0.000s
user    0m0.000s
sys     0m0.000s
$ /bin/time echo 1
1
0.00user 0.00system 0:00.00elapsed 50%CPU (0avgtext+0avgdata 2392maxresident)k
64inputs+0outputs (1major+134minor)pagefaults 0swaps
$ [ -v a ]
$ /bin/[ -v a ]
/bin/[: ‘-v’: unary operator expected

粗略地说,当 shell 解释器不可用或不需要时,使用内置程序版本的程序版本。让我们更详细地解释一下...

当你运行一个 shell 脚本时,解释器会识别内置的,并且不会fork/exec,而只是调用与内置的函数对应的函数。即使你从 C/C++ 可执行文件调用它们system(),后者首先启动一个 shell,然后让 spawn shell 运行内置的 .
下面是一个示例程序,由于system()库服务,它可以echo message运行:

#include <stdlib.h>
int main(void)
{
system("echo message");
return 0;
}

编译并运行它:

$ gcc msg.c -o msg
$ ./msg
message

使用-f选项在strace下运行后者会显示所涉及的进程。主程序执行:

$ strace -f ./msg
execve("./msg", ["./msg"], 0x7ffef5c99838 /* 58 vars */) = 0

然后,system()触发一个fork(),这实际上是一个clone()系统调用。启动子进程 #5185

clone(child_stack=0x7f7e6d6cbff0, flags=CLONE_VM|CLONE_VFORK|SIGCHLD
strace: Process 5185 attached
<unfinished ...>

子进程执行/bin/sh -c "echo message"。后一个 shell 调用内置的echo在屏幕上显示消息(write()系统调用):

[pid  5185] execve("/bin/sh", ["sh", "-c", "echo message"], 0x7ffdd0fafe28 /* 58 vars */ <unfinished ...>
[...]
[pid  5185] write(1, "messagen", 8message
)    = 8
[...]
+++ exited with 0 +++

当您为了性能而需要从没有中间 shell 的 C/C++ 可执行文件中获取它们时,内置的程序版本很有用。例如,当您通过execv()函数调用它们时.
这是一个示例程序,它执行与前面的示例相同的操作,但使用execv()而不是system()

#include <unistd.h>
int main(void)
{
char *av[3];
av[0] = "/bin/echo";
av[1] = "message";
av[2] = NULL;
execv(av[0], av);
return 0;
}

编译并运行它以查看我们得到相同的结果:

$ gcc msg1.c -o msg1
$ ./msg1
message

让我们在strace下运行它以获取详细信息。输出较短,因为不涉及执行中间 shell 的子进程。而是执行实际的/bin/echo程序:

$ strace -f ./msg1
execve("./msg1", ["./msg1"], 0x7fffd5b22ec8 /* 58 vars */) = 0
[...]
execve("/bin/echo", ["/bin/echo", "message"], 0x7fff6562fbf8 /* 58 vars */) = 0
[...]
write(1, "message 1n", 10message 
)            = 10
[...]
exit_group(0)                           = ?
+++ exited with 0 +++

当然,如果程序应该做其他事情,那么对execv()的简单调用是不够的,因为它会被/bin/echo程序覆盖自身。一个更详细的程序将分叉并执行后一个程序,但不需要运行中间 shell:

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
if (fork() == 0) {
char *av[3];
av[0] = "/bin/echo";
av[1] = "message";
av[2] = NULL;
execv(av[0], av);
}
wait(NULL);
// Make additional things before ending
return 0;
}

strace下编译并运行它,以查看中间子进程在不需要中间 shell 的情况下执行/bin/echo程序:

$ gcc msg2.c -o msg2
$ ./msg2
message
$ strace -f ./msg2
execve("./msg2", ["./msg2"], 0x7ffc11a5e228 /* 58 vars */) = 0
[...]
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLDstrace: Process 5703 attached
, child_tidptr=0x7f8e0b6e0810) = 5703
[pid  5703] execve("/bin/echo", ["/bin/echo", "message"], 0x7ffe656a9d08 /* 58 vars */ <unfinished ...>
[...]
[pid  5703] write(1, "messagen", 8message
)    = 8
[...]
[pid  5703] +++ exited with 0 +++
<... wait4 resumed>NULL, 0, NULL)       = 5703
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5703, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
exit_group(0)                           = ?
+++ exited with 0 +++

相关内容

最新更新