为什么在这个 git 钩子示例的末尾使用 exec(看似不必要)?
Why is exec used (seemingly unnecessarily) at the end of this git hook sample?
我正在使用我在 OSX (git version 2.24.3 (Apple Git-128)
) 上的版本附带的 commit.sample 之前的 githooks。代码中有一些特殊之处,即与看似虚假的 exec
.
有关
预提交示例包含以下代码(已删除不相关的 lines/blocks):
#!/bin/sh
against=HEAD
# Redirect output to stderr.
exec 1>&2
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
如果我试图通过在最后一次 exec
调用之后附加验证来修改此代码,它永远不会运行。根据 relevant AskUbuntu post,我明白 exec
是什么让这一切发生了。
但是,我不明白为什么 exec
需要首先发生。如果有尾随空格,此行的挂钩会失败,但如果我删除 exec
并直接调用 git diff-index ...
.
,它的行为似乎相同
换句话说,这个:
git diff-index --check --cached $against --
...看起来像这样:
exec git diff-index --check --cached $against --
...除了后者似乎更具限制性。我找不到带有或不带有 exec
、 除了 的文件之间的区别 exec
使得空白检查 有 最后发生。
为什么示例创建者会选择 exec
选项,当它看起来与表面上限制较少的直接调用相同时?
这可能是(也许是误入歧途的)提高效率的尝试。
一般来说,在shell脚本中,脚本中的return值是最后一个命令运行的值,如。所以:
#! /bin/sh
cmd1
cmd2
cmd3
exit $?
只是一种long-winded/明确的做法:
#! /bin/sh
cmd1
cmd2
cmd3
这里的一般规则是 shell 采用每个“管道”——一个管道被定义为一系列带有 |
符号的命令,这些符号相互连接——并且 运行 是主 shell 进程的 fork-then-exec 中的管道。所以:
cmd1 | cmd2
cmd3
使主 shell 分叉一次到 运行 cmd1 | cmd2
(在内部,这两个命令中的每一个都需要另一个分叉),然后再次分叉到 运行 命令 3。然后,运行 退出命令,shell 将以 $?
(最后一个管道的退出状态)作为它自己的状态退出。
添加重定向,例如:
cmd1 | cmd2 > file
“意味着”shell 应该分叉,然后 运行 管道 cmd1 | cmd2
将其输出重定向到该文件。当然 cmd1
的输出已经重定向到 cmd2
的输入,所以这里只有 cmd2
的输出受到影响——但我们可以看到 cmd3
的输出是not 重定向,很明显,重定向没有发生在 shell 级别,而是发生在 sub-shell 级别分叉到 运行 管道。1
exec
关键字的作用实际上是防止分叉。即:
exec cmd > out
重定向发生在 顶层 shell,然后 运行 使用 exec
系统调用发送给定命令无需先调用 fork
。此 将 shell 替换为 运行 命令(但会保留进程 ID 和所有打开的文件描述符,直到 运行 到此结束)。
如果我们省略命令本身,我们得到:
exec >out
这意味着没有命令获得 运行,但是重定向发生在 shell 本身 而不是某些 sub-shell。所以现在每个后续命令都会得到 fork-and-exec,并将其输出发送到文件 out
.
我们在您自己的脚本中看到类似的内容:
exec 1>&2
强制所有后续命令的 stdout 转到与 stderr 相同的文件描述符。
奇怪的是,只有一个后续命令,这意味着如果目标是效率,他们可以使用:
exec git diff-index --check --cached $against -- 1>&2
将所有内容放在一行中。
1实际上,shells 实际上会提前打开文件,并且必须做很多花哨的步法来在 fork
和 exec
调用。使用 POSIX 风格的作业控制,情况更糟:shell 必须做很多 signal-directing 工作,创建进程组,等等。编写 shell 是 困难的 ,正如 V8 Unix 和 Plan 9 人员所见,这意味着整个 OS 设计需要一些修改。
一般退出状态
如您在回复中所述:
Hence, if I have validation after a non-execed command, I'd need to make sure check for a non-0 result from the git diff-index
.
是的。请注意,通常 shells(特别是 /bin/sh
)具有有趣的标志,您可以从命令或 #!
行或使用 set
命令设置这些标志。这些标志之一是 e
标志,如果命令具有 non-zero 退出代码,它会使 shell 退出:2
#! /bin/sh -e
cmd1
cmd2
cmd3
大致相当于:
#! /bin/sh
cmd1 || exit
cmd2 || exit
cmd3
(我们不需要最后一个 || exit
,尽管我们可以无害地使用它)。 -e
标志通常是个好主意。
2注意tested命令不会让shell立即退出,所以我们可以这样写:
if grep ...; then
thing to run when regexp is found
else
thing to run when regexp is not found
fi
在 /bin/sh
的一些早期版本中存在一个错误,它不能正常工作:我记得修复它,然后发现我要么 over-fixed 要么 under-fixed 它对于像 a && b || c
这样的情况,必须 re-fix 它。
我正在使用我在 OSX (git version 2.24.3 (Apple Git-128)
) 上的版本附带的 commit.sample 之前的 githooks。代码中有一些特殊之处,即与看似虚假的 exec
.
预提交示例包含以下代码(已删除不相关的 lines/blocks):
#!/bin/sh
against=HEAD
# Redirect output to stderr.
exec 1>&2
# If there are whitespace errors, print the offending file names and fail.
exec git diff-index --check --cached $against --
如果我试图通过在最后一次 exec
调用之后附加验证来修改此代码,它永远不会运行。根据 relevant AskUbuntu post,我明白 exec
是什么让这一切发生了。
但是,我不明白为什么 exec
需要首先发生。如果有尾随空格,此行的挂钩会失败,但如果我删除 exec
并直接调用 git diff-index ...
.
换句话说,这个:
git diff-index --check --cached $against --
...看起来像这样:
exec git diff-index --check --cached $against --
...除了后者似乎更具限制性。我找不到带有或不带有 exec
、 除了 的文件之间的区别 exec
使得空白检查 有 最后发生。
为什么示例创建者会选择 exec
选项,当它看起来与表面上限制较少的直接调用相同时?
这可能是(也许是误入歧途的)提高效率的尝试。
一般来说,在shell脚本中,脚本中的return值是最后一个命令运行的值,如
#! /bin/sh
cmd1
cmd2
cmd3
exit $?
只是一种long-winded/明确的做法:
#! /bin/sh
cmd1
cmd2
cmd3
这里的一般规则是 shell 采用每个“管道”——一个管道被定义为一系列带有 |
符号的命令,这些符号相互连接——并且 运行 是主 shell 进程的 fork-then-exec 中的管道。所以:
cmd1 | cmd2
cmd3
使主 shell 分叉一次到 运行 cmd1 | cmd2
(在内部,这两个命令中的每一个都需要另一个分叉),然后再次分叉到 运行 命令 3。然后,运行 退出命令,shell 将以 $?
(最后一个管道的退出状态)作为它自己的状态退出。
添加重定向,例如:
cmd1 | cmd2 > file
“意味着”shell 应该分叉,然后 运行 管道 cmd1 | cmd2
将其输出重定向到该文件。当然 cmd1
的输出已经重定向到 cmd2
的输入,所以这里只有 cmd2
的输出受到影响——但我们可以看到 cmd3
的输出是not 重定向,很明显,重定向没有发生在 shell 级别,而是发生在 sub-shell 级别分叉到 运行 管道。1
exec
关键字的作用实际上是防止分叉。即:
exec cmd > out
重定向发生在 顶层 shell,然后 运行 使用 exec
系统调用发送给定命令无需先调用 fork
。此 将 shell 替换为 运行 命令(但会保留进程 ID 和所有打开的文件描述符,直到 运行 到此结束)。
如果我们省略命令本身,我们得到:
exec >out
这意味着没有命令获得 运行,但是重定向发生在 shell 本身 而不是某些 sub-shell。所以现在每个后续命令都会得到 fork-and-exec,并将其输出发送到文件 out
.
我们在您自己的脚本中看到类似的内容:
exec 1>&2
强制所有后续命令的 stdout 转到与 stderr 相同的文件描述符。
奇怪的是,只有一个后续命令,这意味着如果目标是效率,他们可以使用:
exec git diff-index --check --cached $against -- 1>&2
将所有内容放在一行中。
1实际上,shells 实际上会提前打开文件,并且必须做很多花哨的步法来在 fork
和 exec
调用。使用 POSIX 风格的作业控制,情况更糟:shell 必须做很多 signal-directing 工作,创建进程组,等等。编写 shell 是 困难的 ,正如 V8 Unix 和 Plan 9 人员所见,这意味着整个 OS 设计需要一些修改。
一般退出状态
如您在回复中所述:
Hence, if I have validation after a non-execed command, I'd need to make sure check for a non-0 result from the
git diff-index
.
是的。请注意,通常 shells(特别是 /bin/sh
)具有有趣的标志,您可以从命令或 #!
行或使用 set
命令设置这些标志。这些标志之一是 e
标志,如果命令具有 non-zero 退出代码,它会使 shell 退出:2
#! /bin/sh -e
cmd1
cmd2
cmd3
大致相当于:
#! /bin/sh
cmd1 || exit
cmd2 || exit
cmd3
(我们不需要最后一个 || exit
,尽管我们可以无害地使用它)。 -e
标志通常是个好主意。
2注意tested命令不会让shell立即退出,所以我们可以这样写:
if grep ...; then
thing to run when regexp is found
else
thing to run when regexp is not found
fi
在 /bin/sh
的一些早期版本中存在一个错误,它不能正常工作:我记得修复它,然后发现我要么 over-fixed 要么 under-fixed 它对于像 a && b || c
这样的情况,必须 re-fix 它。