注意:以下适用于所有主要的 POSIX 兼容 shell,除非另有说明:bash
、dash
、ksh
和zsh
. ( ,Debian Almquist Shell,是基于 Debian 的 Linux 发行版(例如 Ubuntu)上dash
的默认 shell ( )。sh
unset
具有其原始含义-可以使用其选项取消定义 shell函数-f
的内置命令- 是确保任何其他 shell 关键字、命令或内置命令具有其原始含义的关键。
- 从未修改的 开始
unset
,您可以确保未修改shopt
和/或command
,它们一起可以用来绕过或取消定义可能影响 shell 关键字、内置函数和外部实用程序的任何别名或 shell 函数。
- 作为未定义函数的替代方法,
command
可用于通过环境绕过它们,包括那些可能已在代码之外定义的函数;仅支持
导出功能只是这些机制之一;不同的外壳有不同的外壳,可能支持多个 - 见下文。bash
只有dash
,ksh
和bash
when 在 POSIX 兼容模式下保证unset
没有重新定义:
可悲的是,据我所知,在zsh
- 以及bash
默认模式下 -无法保证unset
自身没有被重新定义,并且可能还有其他类似 POSIX 的 shell 的行为类似。
- 将其称为
\unset
(引用名称的任何部分)将绕过别名重新定义,但不会绕过函数重新定义 - 并且要撤消您需要原来的unset
本身:catch 22。
因此,在无法控制执行环境的情况下,您无法编写完全不受篡改的 shell 脚本,除非您知道您的代码将由dash
、ksh
或bash
(使用解决方法)执行。
附加信息:
根据 POSIX,引用命令名称的任何部分(例如,\unset
)会绕过该名称的任何别名形式或关键字形式(POSIX 和用语中的保留字zsh
) - 但不是shell函数。
根据 POSIX,unalias -a
取消定义所有别名。没有等效的、符合 POSIX 的命令来取消定义所有函数。
- 警告:旧
zsh
版本不支持-a
;但是,至少从 开始v5.0.8
,它们确实如此。
Builtincommand
可用于绕过, 和- 中的关键字、别名、函数bash
,换句话说:仅执行builtins和外部实用程序。相比之下,默认情况下也会绕过内置函数;要执行内置函数,请使用. dash
ksh
command
zsh
zsh
options[POSIX_BUILTINS]=on
以下可用于在所有 shell 中执行仅命名的外部实用程序:
请注意,虽然它不是 POSIX 实用程序,但它在现代类 Unix 平台上广泛可用。<name>
"$(command which <name>)" ...
which
命令形式的优先级:
bash
, zsh
: 别名 > shell 关键字 > shell 函数 > 内置 > 外部实用程序
ksh
, dash
: shell 关键字 > 别名 > shell 函数 > 内置 > 外部实用程序
- 即: in
bash
和zsh
别名可以覆盖 shell 关键字,而 inksh
和dash
它不能。
bash
, ksh
, 和zsh
- 但不是dash
- 都允许使用非标准函数签名 ,function <name> { ...
作为 POSIX 兼容<name>() { ...
形式的替代方案。
- 语法是以下条件的
function
先决条件:
- 确保在定义函数之前
<name>
它本身不受别名扩展的影响。
- 能够选择一个
<name>
也是 shell关键字的;
请注意,这样的函数只能以带引号的形式调用;例如,\while
。
- (在 的情况下
ksh
,使用function
语法还意味着typeset
语句创建局部变量。)
dash
, ksh
, 和bash
当处于 POSIX 模式下时,还会额外阻止特殊内置函数的命名函数(例如,, unset
, break
, set
)shift
;可以在此处找到 POSIX 定义的特殊内置函数列表;bothdash
并ksh
添加一些不能重新定义的更多内容(例如,local
in dash
;typeset
和unalias
in ksh
),但是两个 shell 都有可以重新定义的额外的非特殊内置函数(例如,)。
请注意,在上述规则的情况下,无论是否使用语法都适用。type
ksh
function
您的代码范围内的环境 shell 函数的潜在来源:
注意:防止这些问题的最简单方法是在您想要调用内置或外部实用程序时使用(未修改的)command
内置(在zsh
with中,以防止绕过内置)。options[POSIX_BUILTINS]=on
ENV
POSIX 要求将环境变量中由其绝对路径指定的脚本作为交互式shell的来源(有一些限制 - 请参阅规范);并始终尊重这一点,而仅在以 or 调用时才这样做,在 v4.2+ 中,使用; 相反,从不尊重这个变量。ksh
dash
bash
sh
--posix
zsh
- 注意:作为脚本运行的代码通常会在非交互式shell 中运行,但这不能保证;例如,您的代码可能来自交互式脚本,或者有人可以调用您的脚本,例如
sh -i
强制交互式实例。
bash
有2个机制:
- 使用or导出单个函数(其他 shell 仅支持导出变量)
export -f
declare -fx
- 每当在可选环境变量中启动非交互式shell时,指定要源的脚本的完整路径
BASH_ENV
。
ksh
支持通过可选的环境变量自动加载FPATH
函数:包含位于任何指定目录中的函数定义的文件FPATH
被隐式自动加载。
- (也
zsh
支持FPATH
,但是自动加载函数需要一个明确 autoload <name>
的声明,所以除非你特别要求一个给定名称的函数来自动加载,否则不会将任何函数添加到你的 shell 中。)
zsh
支持通过其和初始化文件为任何 zsh
实例(无论是否交互)采购脚本。/etc/zshenv
~/.zhsenv
(dash
似乎不支持通过环境定义功能的任何机制。)
解决方法bash
:确保unset
具有其原始含义:
仅当您知道bash
将执行您的脚本时,此解决方法才是安全的,不幸的是,它本身不能得到保证。
此外,因为它修改了 shell 环境(删除别名和函数),所以它不适合设计为 source的脚本。
如前所述,通常不希望在 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
以下命令模拟了一个被别名和函数遮蔽的篡改环境,并且 filescript
是sourced,导致它看到函数,并且在交互式获取时也扩展别名:
$ (unset() { echo hi; }; alias unset='echo here'; . ./script)
unset is a shell builtin
type unset
输出unset is a shell builtin
证明该函数和隐藏内置函数的别名unset
均已停用。