我的意思是我想使用unset
本身不是shell函数。如果可以的话,我可以通过运行来确保command
是纯净的
#!/bin/sh
{ \unset -f unalias command [; \unalias unset command [ } 2>/dev/null;
# make zsh find *builtins* with `command` too:
[ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on
如果我使用的是Debian Almquist shell(破折号),我想我可以相信
\unset
是纯文本。至少我不能在unset
中定义名为dash
的shell函数。在bash
或zsh
中,我可以定义unset() { echo fake unset; }
,此后我无法取消设置该功能:\unset -f unset
输出“ fake unset”。与此相关,在
bash
脚本中,可以通过export -f <function name>
导出函数,以便可以在该脚本调用的bash
脚本中使用它。但是,这在dash
脚本中不起作用。我想知道,如果我使用的是dash
,是否不必担心将命令定义为正在编写的脚本文件之外的shell函数?其他POSIX兼容的外壳怎么样?
最佳答案
注意:以下内容适用于所有主要的POSIX兼容外壳程序,除非另有说明:bash
,dash
,ksh
和zsh
。 (dash
,Debian Almquist Shell,是基于Debian的Linux发行版(如Ubuntu)上的默认Shell(sh
))。unset
具有其原始含义-a builtin that can undefine shell functions with its -f
option-是确保任何其他shell关键字,命令或内建函数具有其原始含义的关键。
从未修改的unset
开始,可以确保未修改的shopt
和/或command
,并且它们可以一起用于绕过或取消定义可能遮盖shell关键字,内置函数和外部实用程序的任何别名或shell函数。
作为未定义函数的替代方法,可以使用command
绕过它们,包括通过环境绕过它们,包括那些可能在代码外部定义的函数。
仅bash
支持的导出功能只是这些机制之一;不同的外壳有不同的外壳,可能支持多个外壳-参见下文。
在POSIX兼容模式下,只有dash
,ksh
和bash
保证未重新定义unset
:dash
和ksh
是安全的,因为正如您所发现的,它们不允许定义名为unset
的函数,并且可以通过调用\unset
来绕过任何别名形式。
在POSIX兼容模式下,bash
允许您定义一个名为unset
的函数,但是在调用unset
时将其忽略,并始终执行该内置函数,这将在以后发现。
鉴于POSIX兼容模式限制了Bash的功能集并修改了其行为,通常不希望在其中运行Bash代码。这篇文章的底部是您建议的解决方法的实现,该方法临时激活POSIX兼容模式,以确保未定义任何unset
函数。
不幸的是,据我所知,在zsh
以及bash
的默认模式下,无法保证unset
本身没有被重新定义,并且可能还有其他类似POSIX的外壳表现类似。
以\unset
调用它(引用名称的任何部分)将绕过别名重定义,而不是函数重定义-并撤消您将需要原始的unset
本身:捕获22。
因此,在无法控制执行环境的情况下,您无法编写完全不受篡改的shell脚本,除非您知道您的代码将由dash
,ksh
或bash
执行(有适当的解决方法) )。
如果您愿意假定unset
未被篡改,则最可靠的方法是:
使用\unset -f
确保unalias
和command
未被修改(没有被shell函数遮盖:\unset -f unalias command
)
与别名不同,函数必须按名称明确地未定义,但是并非所有的shell都提供一种枚举所有已定义函数的机制(不幸的是,typeset -f
在bash
,ksh
和zsh
中起作用,但是dash
appears to have no mechanism at all ),因此不一定总是无法定义所有功能。
使用\unalias -a
删除所有别名。
然后用command [-p]
调用除定义的功能以外的所有内容。调用外部实用程序时,请尽可能使用显式路径,和/或在使用标准实用程序的情况下,请使用command -p
,它使用仅限于标准位置的最小$PATH
定义(运行command -p getconf PATH
可以看到该定义)。
附加信息:
对于每个POSIX,用命令名引用命令名称的任何部分(例如\unset
)都会绕过该名称的任何别名形式或关键字形式(在POSIX和zsh
措辞中保留的单词)-但不提供Shell函数。
对于POSIX,unalias -a
取消定义所有别名。没有等效的,符合POSIX的命令来取消定义所有功能。
注意:较旧的zsh
版本不支持-a
;至少从v5.0.8
开始,但是确实如此。
内置command
可用于绕过关键字,别名,bash
,dash
和ksh
中的函数-换句话说:command
仅执行内置函数和外部实用程序。相比之下,默认情况下zsh
也绕过内置函数。要使zsh
也执行内置函数,请使用options[POSIX_BUILTINS]=on
。
以下内容可用于在所有shell上仅执行名为<name>
的外部实用程序:"$(command which <name>)" ...
请注意,尽管which
不是POSIX实用程序,但在类似Unix的现代平台上可以广泛使用。
命令格式的优先顺序:bash
,zsh
:别名> shell关键字> shell函数>内置>外部实用程序ksh
,dash
:shell关键字>别名> shell函数>内置>外部实用程序
即:在bash
和zsh
中,别名可以覆盖shell关键字,而在ksh
和dash
中,别名不能。bash
,ksh
和zsh
-但不允许dash
-都允许使用非标准功能签名function <name> { ...
,以替代符合POSIX的<name>() { ...
格式。function
语法是以下条件的先决条件:
在定义函数之前,请确保<name>
本身不受别名扩展的影响。
能够选择也是外壳关键字的<name>
;
注意,这样的函数只能以引号形式调用;例如\while
。
(对于ksh
,使用function
语法还意味着typeset
语句创建局部变量。)
在POSIX模式下,dash
,ksh
和bash
还会阻止特殊内置函数的命名功能(例如unset
,break
,set
,shift
);可以在here中找到POSIX定义的特殊内置函数的列表; dash
和ksh
都添加了一些无法重新定义的内容(例如,local
中的dash
; typeset
中的unalias
和ksh
),但是两个shell都具有其他非特殊的内置函数可以重新定义(例如,type
)。
请注意,在ksh
的情况下,无论是否使用function
语法,都适用上述规则。
在您的代码范围内,环境外壳函数的潜在来源:
注意:防止这些情况的最简单方法是,每当您要调用内置函数或外部实用程序时,都使用(未修改的)command
内置函数(在zsh
和options[POSIX_BUILTINS]=on
中,也可以防止绕过内置函数)。
POSIX要求将其在环境变量ENV
中的绝对路径指定的脚本用于交互式外壳程序(有一些限制-请参见the spec); ksh
和dash
总是很荣幸,而bash
仅在作为sh
或在v4.2 +中与--posix
一起调用时才这样做。相比之下,zsh
从不接受此变量。
注意:您的代码作为脚本运行通常会在非交互式shell中运行,但这不能保证;例如,您的代码可能来自交互式脚本,或者有人可以使用sh -i
调用您的脚本来强制执行交互式实例。bash
具有2种机制:
用export -f
或declare -fx
导出单个函数(其他shell仅支持导出变量)
在可选的环境变量BASH_ENV
中启动非交互式shell时,指定脚本源的完整路径。ksh
支持通过可选的FPATH
环境变量自动加载函数:隐式且自动加载包含位于FPATH
指定的任何目录中的函数定义的文件。
(zsh
也支持FPATH
,但是自动加载功能需要明确的autoload <name>
语句,因此,除非您明确要求使用给定名称的功能来自动加载,否则不会将任何功能添加到您的shell中。)zsh
支持通过其zsh
和/etc/zshenv
初始化文件为任何~/.zhsenv
实例(无论是否交互)采购脚本。
(dash
似乎不支持通过环境定义功能的任何机制。)bash
的解决方法:确保unset
具有其原始含义:
仅当您知道bash
将执行您的脚本时,此解决方法才是安全的,但不幸的是,该脚本本身无法得到保证。
另外,由于它修改了外壳环境(去除了别名和函数),因此它不适用于旨在作为源代码的脚本。
如前所述,通常不希望在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
被生成,导致它看到该函数,并且在以交互方式被生成时,也扩展了别名:$ (unset() { echo hi; }; alias unset='echo here'; . ./script)
unset is a shell builtin
type unset
输出unset is a shell builtin
可以证明该功能和遮盖内置unset
的别名均已停用。
关于bash - 您如何使用内置的纯未设置外壳程序?您可以编写不受篡改的shell脚本吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35916983/