在bash中,“exec”和“exit”的区别是什么?

5
我曾经看到过在bash脚本中使用"exit"和"exec"来停止脚本的执行,如果出现错误。例如:
if [ ! -f file ]; then
echo "no file"
exit 1
fi

并且:

if [ ! -f file ]; then
exec echo "no file"
fi

这里的最佳实践是什么?欢迎广泛讨论/解释有关“exec”和“exit”的内容 :)
3个回答

15

exit 用于退出shell,并返回指定的数字退出码(如果省略则返回0)给父进程。它等同于C库例程exit,或从main返回。

exec 替换 shell 为一个新的可执行镜像(仍在同一进程中运行),该镜像最终将退出并向父进程返回某些代码。它大致相当于C库例程execvp

您的第一个示例几乎是正确的方式来停止脚本,如果发生错误。应该这样写:

if [ ! -f file ]; then
    echo "no file" >&2
    exit 1
fi

你的示例中缺少>&2,这会导致错误消息发送到stderr,这是它应该在的地方,而不是stdout,因为它不属于那里。

你的第二个示例是错误的。除了错误消息发错位置外,exec echo将停止脚本(因为/bin/echo将执行其操作然后退出),但它将向父进程返回退出代码0。退出代码0表示成功。在Unix环境下运行的程序必须始终确保在失败时返回非零退出代码。

exec的正确用法是在做一些设置工作之后调用另一种语言编写的长期运行的程序的shell脚本中,并且之后没有其他事情要做,所以没有必要让shell进程挂起。例如:

#! /bin/sh
PATH=/special/directory/for/foo:$PATH
export PATH
exec foo

Zack,谢谢你的回答!那么你的意思是_exec_主要用于在shell脚本完成一些清理和/或配置工作后执行一个长期运行的程序?使用_exec_调用的程序将获取包含_exec_的脚本的PID吗?此外,Bash _exec_还有其他流行的用途吗? - Martin
好的,但是如果从脚本中调用这个长期运行的程序而没有使用_exec_,有什么缺点/区别呢?我的意思是,在这种情况下会有两个PID,并且在脚本中设置的变量可能对长期运行的程序没有影响?还有其他的吗? - Martin
环境变量在长期运行的程序中都是继承的,唯一需要注意的是是否记得将它们导出(export)。如果没有使用 exec 命令,会有两个进程 ID,shell 会一直占用内存,而且你必须记得在下一行写入 exit $? 命令以便将退出代码传递给父进程。就是这样。 - zwol
Zack,谢谢你的解释!然而,似乎不需要使用 exit $??请看这两个例子:http://pastebin.com/Sax5SVGa。正如您所看到的,在第二个例子中,我没有使用 exec,但仍然得到了正确的退出代码。或者我误解了你的意思? - Martin
有时候,shell脚本中最后一个命令的退出码会传递给父进程,但这并不是可靠的事情,即使标准这样说。 - zwol
作为另一个重要特性,使用exec运行长期进程可以确保在像runit、s6等进程监管器下运行时将信号发送到正确的进程。 - clacke

3
命令exit用给定的退出码退出当前shell。命令exec用参数定义的新进程替换当前shell。这也会在进程终止后有效地终止脚本,退出码为新进程的退出码。
你的代码片段中第一个调用内部shell命令echo,然后以退出码1终止shell。第二个代码片段用外部程序echo替换了shell,该程序可能以退出码0终止。
我强烈推荐使用第一种变体。这样可以避免启动外部命令/bin/echo并正确指示退出码表示错误。

0

指示错误的正确方法是脚本返回一个非零代码,例如1。 在您的情况下,最好的方法是使用“exit 1”。

使用“exec”将简单地执行其后的代码。也许您看到的示例正在使用“#!/bin/bash -e”或“set -e”,然后使用“exec”调用失败的代码,从而导致“exec”本身返回一个非零代码,“-e”表示如果任何命令返回非零代码或计算为false,则立即退出脚本。


exec 命令会用新的进程替换当前 shell,无论 set -e 或类似选项是否设置,都会有效地终止脚本。 - Sven Marnach

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接