我写这可能是用于本地命名空间中的任意一种shell内建或功能的POSIX外壳功能ksh93,dash,mksh,或bash (特别命名的,因为我已经亲自证实它工作在所有这些)。在我测试过的外壳中,它仅未能满足我的期望yash,而我从来没有期望它可以在中工作zsh。我没有测试posh。我放弃了posh一段时间的希望,并且已经有一段时间没有安装它了。也许可以在posh...中使用?
我之所以说它是POSIX,是因为通过阅读规范,它利用了基本实用程序的特定行为,但是,诚然,规范在这方面含糊不清,并且至少有一个人与我不同意。总的来说,我对此有不同意见,我最终发现错误是我自己的,也许这次我对规范也有误,但是当我进一步问他时,他没有回答。
就像我说的那样,这绝对可以在前面提到的shell中起作用,并且基本上可以通过以下方式起作用:
some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"
3
empty
该command命令被指定为基本可用的实用程序和预置$PATH的内置程序之一。它的指定功能之一是在调用它们时将特殊的内置实用程序包装在自己的环境中,等等。
{ sh -c ' x=5 set --; echo "$x"
x=6 command set --; echo "$x"
exec <""; echo uh_oh'
sh -c ' command exec <""; echo still here'
}
5
5
sh: 3: cannot open : No such file
sh: 1: cannot open : No such file
still here
...根据规范,以上两个命令行分配的行为都是正确的。两种错误情况的行为也都是正确的,并且实际上在规范中几乎完全被复制。指定功能或特殊内建函数的命令行前缀的分配会影响当前的shell环境。同样,重定向错误在指向其中任何一个时都被指定为致命错误。command在某些情况下,指定____来抑制对特殊内置函数的特殊处理,并且在本规范中通过示例实际演示了重定向情况。
command另一方面,常规内建函数(例如)被指定为在subshell环境中运行-不一定意味着另一个进程,只是它与一个进程基本没有区别。调用常规内建函数的结果应始终类似于从类似功能$PATH的d命令获得的结果。所以...
na=not_applicable_to_read
na= read var1 na na var2 <<"" ; echo "$var1" "$na" "$var2"
word1 other words word2
word1 not_applicable_to_read word2
但是该command命令不能调用shell函数,因此不能像常规内置函数那样使用它们来呈现其特殊对待。这也是规范。实际上,该规范说,的主要用途command是您可以在为另一个命令命名的wrapper shell函数中使用它,而无需自递归调用该另一个命令,因为它不会调用该函数。像这样:
cd(){ command cd -- "$1"; }
如果您在command那里不使用该cd功能,几乎肯定会出现段错误以进行自我递归。
但是作为可以调用特殊内置command程序的常规内置程序,可以在subshell环境中进行调用。因此,虽然当前的shell状态威力棒中定义当前shell -肯定read的$var1,并$var2没有-命令行定义的至少结果很可能不应该...
简单命令
如果没有命令名称,或者命令名称是特殊的内置函数或函数,则变量分配将影响当前的执行环境。否则,应为命令的执行环境导出变量分配,并且不得影响当前的执行环境。
现在,我既不知道是否command既可以作为常规内置程序,又可以直接调用特殊内置程序,这只是命令行定义方面的某种意料之外的漏洞,但我确实知道至少已经有四个shell提到荣誉command命名空间。
尽管command不能直接调用Shell函数,但它可以eval如所示进行调用,因此可以间接进行调用。因此,我在此概念上构建了名称空间包装器。它需要一个参数列表,例如:
ns any=assignments or otherwise=valid names which are not a command then all of its args
...除了command上面的单词只有在找到一个空的时才被识别为一个$PATH。除了本地作用域命名的命令行shell变量,它还本地作用域单小写字母的名称和其他标准的人的名单,如所有变量$PS3,$PS4,$OPTARG,$OPTIND,$IFS,$PATH,$PWD,$OLDPWD和其他一些人。
是的,通过局部定义$PWD和$OLDPWD变量,然后显式定位cd到$OLDPWD,$PWD它也可以相当可靠地确定当前工作目录的范围。尽管确实会尽力,但这并不能保证。它保留一个描述符7<.,当它的包装目标返回时,它会这样做cd -P /dev/fd/7/。如果当前的工作目录已经unlink()在中间,那么它至少仍应设法改回原来的目录,但是在这种情况下会发出难看的错误。并且因为它维护描述符,所以我认为没有一个理智的内核也不应卸载其根设备(???)。
它还在本地作用域外壳程序选项,并在其包装的实用程序返回时将其恢复到找到它们的状态。它$OPTS特别对待它,因为它会在其自己的范围内维护一个副本,该副本最初会为其分配值$-。在命令行上还处理了所有分配之后,它将set -$OPTS在调用其包装目标之前执行此操作。这样,如果您-$OPTS在命令行上定义,则可以定义包装目标的外壳程序选项。目标返回时,它将set +$- -$OPTS具有自己的副本$OPTS (不受命令行定义的影响),并将所有副本恢复到原始状态。
当然,没有什么可以阻止调用者returrn通过包装目标或其参数以某种方式显式地退出函数。这样做将防止原本会尝试的任何状态恢复/清除。
要做到所有这些,需要eval深入研究。首先,它将自身包装在本地范围内,然后从内部读取参数,为有效的外壳程序名称验证参数,如果发现参数名称无效,则退出并出错。如果所有参数都有效,并且最终导致command -v "$1"返回true (请记住:此时$PATH为空),它将eval在命令行中定义并将所有其余参数传递给wrap目标(尽管它忽略了ns- 的特殊情况,因为那样会eval并不是很有用,而深度为3 则足够了。)
它基本上是这样的:
case $- in (*c*) ... # because set -c doesnt work
esac
_PATH=$PATH PATH= OPTS=$- some=vars \
command eval LOCALS=${list_of_LOCALS}'
for a do i=$((i+1)) # arg ref
if [ "$a" != ns ] && # ns ns would be silly
command -v "$a" &&
! alias "$a" # aliases are hard to run quoted
then eval " PATH=\$_PATH OTHERS=$DEFAULTS $v \
command eval '\''
shift $((i-1)) # leave only tgt in @
case $OPTS in (*different*)
set \"-\${OPTS}\" # init shell opts
esac
\"\$@\" # run simple command
set +$- -$OPTS "$?" # save return, restore opts
'\''"
cd -P /dev/fd/7/ # go whence we came
return "$(($??$?:$1))" # return >0 for cd else $1
else case $a in (*badname*) : get mad;;
# rest of arg sa${v}es
esac
fi; done
' 7<.
还有一些其他重定向,并且和一些怪异的测试,做一些炮弹把路c中$-,然后拒绝接受它作为一个选项set (???) ,但它的所有辅助和主要只是用来从发射拯救不必要的输出,在极端情况下类似。这就是它的工作原理。它之所以可以做这些事情,是因为它在嵌套此类中调用其包装的实用程序之前,先设置了自己的本地范围。
很长,因为我在这里要非常小心-三个evals很难。但是,您可以执行以下操作:
ns X=local . /dev/fd/0 <<""; echo "$X" "$Y"
X=still_local
Y=global
echo "$X" "$Y"
still_local global
global
进一步采取这一步骤,并持续命名包装的实用程序的本地范围应该不是很困难。甚至在编写时,它也已经$LOCALS为包装的实用程序定义了一个变量,该变量仅由空格分隔,包含在包装的实用程序的环境中定义的所有名称的列表。
喜欢:
ns var1=something var2= eval ' printf "%-10s%-10s%-10s%s\n" $LOCALS '
...这是绝对安全的- $IFS已清除为默认值,$LOCALS除非您在命令行中自行设置,否则只有有效的外壳程序名称才可以使用。即使拆分变量中可能包含全局字符,您也可以OPTS=f在命令行上对包装的实用程序进行设置,以禁止扩展它们。在任何情况下:
LOCALS ARG0 ARGC HOME
IFS OLDPWD OPTARG OPTIND
OPTS PATH PS3 PS4
PWD a b c
d e f g
h i j k
l m n o
p q r s
t u v w
x y z _
bel bs cr esc
ht ff lf vt
lb dq ds rb
sq var1 var2
这是功能。所有命令都以w / \为前缀,以避免alias扩展:
ns(){ ${1+":"} return
case $- in
(c|"") ! set "OPTS=" "$@"
;; (*c*) ! set "OPTS=${-%c*}${-#*c}" "$@"
;; (*) set "OPTS=$-" "$@"
;; esac
OPTS=${1#*=} _PATH=$PATH PATH= LOCALS= lf='
' rb=\} sq=\' l= a= i=0 v= __=$_ IFS=" ""
" command eval LOCALS=\"LOCALS \
ARG0 ARGC HOME IFS OLDPWD OPTARG OPTIND OPTS \
PATH PS3 PS4 PWD a b c d e f g h i j k l m n \
o p q r s t u v w x y z _ bel bs cr esc ht ff \
lf vt lb dq ds rb sq'"
for a do i=$((i+1))
if \[ ns != "$a" ] &&
\command -v "$a" >&9 &&
! \alias "${a%%=*}" >&9 2>&9
then \eval 7>&- '\' \
'ARGC=$((-i+$#)) ARG0=$a HOME=~' \
'OLDPWD=$OLDPWD PATH=$_PATH IFS=$IFS' \
'OPTARG=$OPTARG PWD=$PWD OPTIND=1' \
'PS3=$PS3 _=$__ PS4=$PS4 LOCALS=$LOCALS' \
'a= b= c= d= e= f= g= i=0 j= k= l= m= n= o=' \
'p= q= r= s= t= u= v= w= x=0 y= z= ht=\ ' \
'cr=^M bs=^H ff=^L vt=^K esc=^[ bel=^G lf=$lf' \
'dq=\" sq=$sq ds=$ lb=\{ rb=\}' \''"$v' \
'\command eval 9>&2 2>&- '\' \
'\shift $((i-1));' \
'case \${OPTS##*[!A-Za-z]*} in' \
'(*[!c$OPTS]*) >&- 2>&9"'\' \
'\set -"${OPTS%c*}${OPTS#*c}"' \
';;esac; "$@" 2>&9 9>&-; PS4= ' \
'\set +"${-%c*}${-#*c}"'\'\" \
-'$OPTS \"\$?\"$sq";' \
' \cd -- "${OLDPWD:-$PWD}"
\cd -P ${ANDROID_SYSTEM+"/proc/self/fd/7"} /dev/fd/7/
\return "$(($??$?:$1))"
else case ${a%%=*} in
([0-9]*|""|*[!_[:alnum:]]*)
\printf "%s: \${$i}: Invalid name: %s\n" \
>&2 "$0: ns()" "'\''${a%%=*}'\''"
\return 2
;; ("$a") v="$v $a=\$$a"
;; (*) v="$v ${a%%=*}=\${$i#*=}"
;; esac
case " $LOCALS " in (*" ${a%%=*} "*)
;; (*) LOCALS=$LOCALS" ${a%%=*}"
;; esac
fi
done' 7<. 9<>/dev/null
}
( easiest thing ever )。但这并不是您所追求的。我猜你可以做( stuff in subshell; exec env ) | sed 's/^/namespace_/',eval并且结果放在父shell中,但这有点讨厌。