我想了解bash内置。因此,以下问题:
- 当我想到内置术语时,我认为
bash
可执行文件在其符号表中定义了一个函数,可执行文件的其他部分可以访问该函数而无需实际fork
。这就是内置的意思吗? - 我还看到一些内置的具有单独的可执行文件。例如,
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 +++