注意:除非另有说明,以下内容适用于所有主要的 POSIX 兼容 shell:bash
, dash
, ksh
, and zsh
. (dash
Debian Almquist Shell 是默认 shell(sh
)在基于 Debian 的 Linux 发行版(例如 Ubuntu)上。
-
unset
有其本来的意义 - 可以取消定义 shell 的内置函数功能以其-f option- 是个确保任何其他 shell 关键字、命令或内置命令具有其原始含义的关键.
- 从未修改的开始
unset
,您可以确保未修改shopt
and/or command
,并且它们一起可用于绕过或取消定义可能隐藏 shell 关键字、内置函数和外部实用程序的任何别名或 shell 函数。
- 作为替代方案未定义功能,
command
可以用来bypass它们,包括那些可能已定义的outside您的代码,通过环境;
出口函数,仅作为bash
支持,只是one这些机制;不同的 shell 有不同的 shell,并且可能支持多个 - 见下文。
-
Only dash
, ksh
, and bash
当处于 POSIX 兼容模式时保证unset
没有重新定义:
dash
and ksh
是安全的,因为他们不允许定义 a function named unset
,正如您所发现的,任何别名形式都可以通过调用 as 来绕过\unset
.
-
bash
, 当处于 POSIX 兼容模式时,允许您define一个名为unset
, but ignores当你调用它时unset
,并且始终执行内置函数,正如您后来发现的那样。
- 鉴于POSIX 兼容模式限制 Bash 的功能集并修改其行为,通常不希望在其中运行 Bash 代码。在这篇文章的底部是一个实现解决方法你建议,哪个暂时地激活 POSIX 兼容模式以确保不会
unset
function被定义为。
-
可悲的是,据我所知,在zsh
- 并且也在bash
的默认模式 - 有没有办法保证unset
本身还没有被重新定义,并且可能还有其他类似 POSIX 的 shell 具有类似的行为。
- 称其为
\unset
(引用名称的任何部分)将绕过alias重新定义,但不是function重新定义 - 要撤消它,您需要原始的unset
本身:第22条。
-
Thus, 由于无法控制执行环境,因此无法编写完全不受篡改的 shell 脚本, unless你知道你的代码将被执行dash
, ksh
, or bash
(解决方法到位).
附加信息:
根据 POSIX,引用命令名称的任何部分(例如,\unset
)绕过任何alias形式或keyword form (保留字在 POSIX 和zsh
说法)用这个名字 - 但是not shell 功能.
-
根据 POSIX,unalias -a
取消定义所有aliases。没有等效的、符合 POSIX 标准的命令来取消所有定义功能.
-
Caveat: older
zsh
版本做not支持-a
;至少截至v5.0.8
然而,他们确实这么做了。
Builtin command
可用于绕过关键字、别名、函数bash
, dash
, and ksh
- 换句话说:command
只执行builtins and 外部实用程序。相比之下,zsh
默认情况下also绕过builtins;使zsh
也执行内置函数,使用options[POSIX_BUILTINS]=on
.
可以使用以下命令来执行external实用程序命名<name>
仅在所有 shell 中:
"$(command which <name>)" ...
请注意,虽然which
不是 POSIX 实用程序,它在现代类 Unix 平台上广泛使用。
-
命令形式的优先级:
-
bash
, zsh
:别名> shell关键字> shell函数>内置>外部实用程序
-
ksh
, dash
:shell关键字>别名>shell函数>内置>外部实用程序
- 即:在
bash
and zsh
别名可以覆盖 shell 关键字,而在ksh
and dash
这不可以。
-
bash
, ksh
, and zsh
- 但不是dash
- 全部允许非标准函数签名,function <name> { ...
,作为 POSIX 兼容的替代方案<name>() { ...
form.
- The
function
syntax is the prerequisite for:
- 确保
<name>
本身不受别名扩展的影响before函数已定义。
- 能够选择一个
<name>
那也是一个壳keyword;
请注意,这样的函数可以only被调用于quoted形式;例如。,\while
.
- (如果是
ksh
, using function
语法还意味着typeset
语句创建local变量。)
-
dash
, ksh
, and bash
当处于 POSIX 模式时另外还可以防止命名函数special内置函数(例如,unset
, break
, set
, shift
);可以找到 POSIX 定义的特殊内置函数的列表here; both dash
and ksh
添加一些无法重新定义的内容(例如,local
in dash
; typeset
and unalias
in ksh
),但是两个外壳都有额外的,非特殊的内置函数can被重新定义(例如,type
).
请注意,在以下情况下ksh
上述规则无论是否适用均适用function
使用或不使用语法。
-
潜在来源环境外壳函数在您的代码范围内:
注意:防止这些的最简单方法是使用(未修改的)command
内置(在zsh
with options[POSIX_BUILTINS]=on
,以防止绕过内置函数)每当您想要调用内置或外部实用程序时。
-
POSIX 要求脚本由环境变量中的绝对路径指定ENV
be sourced for 交互的shell(有一些限制 - 请参阅the spec); ksh
and dash
始终尊重这一点,而bash
仅当调用时才会这样做sh
或者,在 v4.2+ 中,--posix
;相比之下,zsh
从不尊重这个变量。
- 注意:您的代码作为script通常会运行在非交互式shell,但这并不能保证;例如,您的代码可能是sourced从交互式脚本,或者有人可以调用您的脚本,例如
sh -i
强制交互式实例。
-
bash
has 2机制:
- 出口个人功能 with
export -f
or declare -fx
(其他shell仅支持导出变量)
- 每当出现时指定脚本的完整路径非交互式shell 在可选环境变量中启动
BASH_ENV
.
-
ksh
支持自动加载通过可选的功能FPATH
环境变量:包含位于指定的任何目录中的函数定义的文件FPATH
are 隐式且自动地 loaded.
- (
zsh
支持FPATH
也是如此,但自动加载功能需要explicit autoload <name>
声明,因此除非您明确要求自动加载给定名称的函数,否则不会将任何函数添加到您的 shell 中。)
zsh
支持采购脚本any zsh
实例(无论是否交互式)通过其/etc/zshenv
and ~/.zhsenv
初始化文件。
(dash
貌似不支持any通过环境定义函数的机制。)
解决方法bash
: 确保这件事unset
它的本意是:
仅当您知道这一点时,此解决方法才是安全的bash
将执行您的脚本,不幸的是,这本身无法得到保证。
此外,由于它修改了 shell 环境(删除别名和函数),因此它不适合以下脚本:设计用于采购.
如前所述,通常不希望在 Bash 的 POSIX 兼容模式下运行代码,但您可以暂时地激活它以确保unset
没有被函数遮盖:
#!/bin/bash
# *Temporarily* force Bash into POSIX compatibility mode, where `unset` cannot
# be shadowed, which allows us to undefine any `unset` *function* as well
# as other functions that may shadow crucial commands.
# Note: Fortunately, POSIXLY_CORRECT= works even without `export`, because
# use of `export` is not safe at this point.
# By contrast, a simple assignment cannot be tampered with.
POSIXLY_CORRECT=
# If defined, unset unset() and other functions that may shadow crucial commands.
# Note the \ prefix to ensure that aliases are bypassed.
\unset -f unset unalias read declare
# Remove all aliases.
# (Note that while alias expansion is off by default in scripts, it may
# have been turned on explicitly in a tampered-with environment.)
\unalias -a # Note: After this, \ to bypass aliases is no longer needed.
# Now it is safe to turn POSIX mode back off, so as to reenable all Bash
# features.
unset POSIXLY_CORRECT
# Now UNDEFINE ALL REMAINING FUNCTIONS:
# Note that we do this AFTER switching back from POSIX mode, because
# Bash in its default mode allows defining functions with nonstandard names
# such as `[` or `z?`, and such functions can also only be *unset* while
# in default mode.
# Also note that we needn't worry about keywords `while`, `do` and `done`
# being shadowed by functions, because the only way to invoke such functions
# (which you can only define with the nonstandard `function` keyword) would
# be with `\` (e.g., `\while`).
while read _ _ n; do unset -f "$n"; done < <(declare -F)
# IN THE REST OF THE SCRIPT:
# - It is now safe to call *builtins* as-is.
# - *External utilities* should be invoked:
# - by full path, if feasible
# - and/or, in the case of *standard utilities*, with
# command -p, which uses a minimal $PATH definition that only
# comprises the locations of standard utilities.
# - alternatively, as @jarno suggests, you can redefine your $PATH
# to contain standard locations only, after which you can invoke
# standard utilities by name only, as usual:
# PATH=$(command -p getconf PATH)
# Example command:
# Verify that `unset` now refers to the *builtin*:
type unset
测试命令:
假设上面的代码被保存到文件中script
在当前目录中。
以下命令模拟一个被篡改的环境,其中unset
被别名和函数所遮蔽,并且文件script
is sourced,使其看到该函数,并且当以交互方式获取时,也扩展别名:
$ (unset() { echo hi; }; alias unset='echo here'; . ./script)
unset is a shell builtin
type unset
输出unset is a shell builtin
证明函数和别名都遮盖了内置函数unset
被停用。