gpt4 book ai didi

linux - 需要对 Linux bash 内置 exec 命令行为的解释

转载 作者:IT老高 更新时间:2023-10-28 12:38:42 26 4
gpt4 key购买 nike

来自 Bash Reference Manual我得到以下关于 exec 的信息bash 内置命令:

If command is supplied, it replaces the shell without creating a new process.



现在我有以下 bash脚本:
#!/bin/bash
exec ls;
echo 123;
exit 0

这执行了,我得到了这个:
cleanup.sh  ex1.bash  file.bash  file.bash~  output.log
(files from the current directory)

现在,如果我有这个脚本:
#!/bin/bash
exec ls | cat
echo 123
exit 0

我得到以下输出:
cleanup.sh
ex1.bash
file.bash
file.bash~
output.log
123

我的问题是:

如果当 exec被调用它会在不创建新进程的情况下替换 shell,为什么 put | cat , echo 123被打印,但没有它,它不是。所以,如果有人能解释这种行为的逻辑是什么,我会很高兴。

谢谢。

编辑:
在@torek 回复之后,我的行为变得更难解释了:

1. exec ls>out命令创建 out文件并将其放入 ls的命令结果;

2. exec ls>out1 ls>out2只创建文件,但不要放入任何结果。如果命令按建议工作,我认为命令编号 2 应该与命令编号 1 具有相同的结果(甚至更多,我认为它不应该创建 out2 文件)。

最佳答案

在这种特殊情况下,您有 exec在一个管道中。为了执行一系列管道命令,shell 必须首先 fork,创建一个子 shell。 (具体来说,它必须创建管道,然后 fork ,以便管道“左侧”运行的所有内容都可以将其输出发送到管道“右侧”的任何内容。)

要了解这实际上是正在发生的事情,请比较:

{ ls; echo this too; } | cat

和:
{ exec ls; echo this too; } | cat

前者运行 ls不离开子shell,因此这个子shell仍然可以运行 echo .后者运行 ls通过离开子 shell ,因此不再需要执行 echo , 和 this too不打印。

(使用花括号 { cmd1; cmd2; } 通常会抑制您使用括号 (cmd1; cmd2) 获得的子 shell 叉 Action ,但在管道的情况下,叉是“强制”的,就像它一样。)

当前 shell 的重定向仅在没有“无事可运行”时发生,就像在 exec 之后一样。 .因此,例如, exec >stdout 4<input 5>>append修改当前 shell,但 exec foo >stdout 4<input 5>>append尝试执行命令 foo . [注意:这不是严格准确的;见附录。]

有趣的是,在交互式 shell 中,在 exec foo >output 之后失败,因为没有命令 foo , shell 仍然存在,但标准输出仍然重定向到文件 output . (您可以使用 exec >/dev/tty 恢复。在脚本中, exec foo 失败会终止脚本。)

向@Pumbaa80 致敬,这里有一些更能说明问题的内容:
#! /bin/bash
shopt -s execfail
exec ls | cat -E
echo this goes to stdout
echo this goes to stderr 1>&2

(注意: cat -E 是从我通常的 cat -vET 简化下来的,这是我方便的“让我以可识别的方式看到非打印字符”的方法)。运行此脚本时, ls 的输出有 cat -E应用(在 Linux 上这使得行尾可见为 $ 符号),但发送到 stdout 和 stderr(在其余两行上)的输出不会被重定向。更改 | cat -E> out并在脚本运行后,观察文件 out 的内容: 最后两个 echo s不在那里。

现在更改 lsfoo (或其他一些找不到的命令)并再次运行脚本。这次的输出是:
$ ./demo.sh
./demo.sh: line 3: exec: foo: not found
this goes to stderr

和文件 out现在有第一个 echo制作的内容线。

这使得什么 exec “真的”尽可能明显(但不再明显,因为阿尔伯特爱因斯坦没有说:-))。

通常,当 shell 执行“简单命令”时(请参阅手册页以获得精确定义,但这明确排除了“管道”中的命令),它准备任何由 < 指定的 I/O 重定向操作。 , > ,等等,通过打开所需的文件。然后 shell 调用 fork (或一些等效但更有效的变体,如 vforkclone,具体取决于底层操作系统、配置等),并在子进程中重新排列打开的文件描述符(使用 dup2 调用或等效项)以实现所需的最终安排: > out将打开的描述符移动到 fd 1-stdout-while 6> out将打开的描述符移动到 fd 6。

如果您指定 exec但是,shell 会抑制关键字 fork步。它像往常一样执行所有文件打开和文件描述符重新排列,但这一次,它会影响任何和所有后续命令。最后,完成所有重定向后,shell 尝试 execve() (在系统调用意义上)命令,如果有的话。如果没有命令,或者如果 execve()调用失败,shell 应该继续运行(是交互式的,或者你已经设置了 execfail ),shell 士兵打开。如果 execve()成功, shell 不再存在,已被新命令取代。如 execfail未设置且 shell 不可交互, shell 退出。

(还有 command_not_found_handle shell 函数的额外复杂性:根据测试结果,bash 的 exec 似乎禁止运行它。通常, exec 关键字会使 shell 不查看自己的函数,即,如果您有一个 shell 函数 f,运行 f 作为一个简单的命令运行 shell 函数,就像 (f) 在子 shell 中运行它一样,但运行 (exec f) 会跳过它。)

至于为什么 ls>out1 ls>out2创建两个文件(有或没有 exec ),这很简单:shell 打开每个重定向,然后使用 dup2移动文件描述符。如果你有两个普通的 >重定向,shell 打开两个,将第一个移动到 fd 1 (stdout),然后将第二个移动到 fd 1 (再次 stdout),关闭进程中的第一个。最后,它运行 ls ls ,因为这是删除 >out1 >out2 后剩下的.只要没有文件名为 ls , ls命令向 stderr 提示,并且不向 stdout 写入任何内容。

关于linux - 需要对 Linux bash 内置 exec 命令行为的解释,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9920512/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com