Shell名称空间


10

有没有一种方法可以source将shell脚本导入命名空间,最好是bash shell脚本,但是如果其他shell具有此功能而bash没有,我会研究它们。

我的意思是,例如,“为所有已定义的符号添加前缀,以免它们与已定义的符号(变量名,函数名,别名)冲突”之类的东西,或防止名称冲突的任何其他功能。

如果有一个可以在source时间上命名空间(NodeJS样式)的解决方案,那将是最好的。

示例代码:

$ echo 'hi(){ echo Hello, world; }' > english.sh
$ echo 'hi(){ echo Ahoj, světe; }' > czech.sh
$ . english.sh
$ hi
 #=> Hello, world
$ . czech.sh #bash doesn't even warn me that `hi` is being overwritten here
$ hi
 #=> Ahoj, světe
#Can't use the English hi now
#And sourcing the appropriate file before each invocation wouldn't be very efficient 

1
感谢您的澄清。我希望答案是否定的。通常的shell编程范例是,当您想隔离更改时,可以在一个子shell中进行操作,而创建一个子shell就与( easiest thing ever )。但这并不是您所追求的。我猜你可以做( stuff in subshell; exec env ) | sed 's/^/namespace_/'eval并且结果放在父shell中,但这有点讨厌。
Celada

3
是。得到ksh93。命名空间是它的基础-它的所有名称类型(也可以键入)都支持命名空间。bash顺便说一下,实际上在各个方面它都比快得多。
mikeserv

@mikeserv谢谢,如果您将它添加为带有演示该功能的代码示例的答案,我会接受的。
PSkocik

@michas我也需要命名空间功能符号和别名。env | sed ...可以用于变量,我可以set获取函数,但是搜索和替换将是一个问题-函数可以相互调用,因此您需要用前缀交叉调用替换所有交叉调用,但不能替换在函数定义代码的其他地方使用相同的词,而不是调用。为此,您需要一个bash解析器,而不仅仅是一个正则表达式,并且只要函数不通过eval相互调用,它就仍然可以工作。
PSkocik

Answers:


11

man ksh安装了系统的系统上ksh93...

  • 名称空间
    • 作为namespace修改变量或创建新变量的命令列表的一部分执行的命令和功能,将创建一个新变量,其名称为名称空间的名称,该名称空间由标识符加上.。当引用名称为name的变量时,首先使用进行搜索.identifier.name
    • 类似地,名称空间列表中的命令定义的功能是使用名称空间名称(以开头)创建的.
    • 当名称空间命令的列表包含namespace命令时,所创建的变量和函数的名称由变量或函数名组成,该变量或函数名之前带有标识符列表,每个标识符之前都带有.。在名称空间之外,可以通过在名称空间内创建的变量或函数前面加上名称空间名称来对其进行引用。
    • 默认情况下,以星号开头的变量.sh位于sh名称空间中。

并且,为演示起见,这是应用于默认情况下为外壳程序中分配的每个常规外壳程序变量提供的名称空间的概念ksh93。在下面的示例中,我将定义一个discipline函数,该函数将作为shell变量的分配.get方法$PS1。每一个shell变量基本上都有自己的命名空间,至少,默认getsetappend,和unset方法。定义以下函数后,只要$PS1在外壳中引用该变量,该变量的输出date就会显示在屏幕顶部。

function PS1.get {
    printf "\0337\33[H\33[K%s\0338" "${ date; }"
}

(还要注意,()上述命令替换中缺少子shell)

从技术上讲,命名空间学科完全相同同样的事情(因为学科可以被定义为全局或局部应用到特定的命名空间,但它们都是重要组成部分,以壳的数据类型的概念化这是根本ksh93

要解决您的特定示例:

echo 'function hi { echo Ahoj, světe\!;  }' >  czech.ksh
echo 'function hi { echo Hello, World\!; }' >english.ksh
namespace english { . ./english.ksh; }
namespace czech   { . ./czech.ksh;   }
.english.hi; .czech.hi

Hello, World!
Ahoj, světe!

...要么...

for ns in czech english
do  ".$ns.hi"
done

Ahoj, světe!
Hello, World!

@PSkocik-您为什么不解决我的Ehoj问题?我本该宣誓就是那句话……对不起。我不会接受甚至不费力地将我在问题中使用的单词拼写正确的人写的答案...不过,老实说,我想我记得我只是复制/粘贴jt ...嗯...
mikeserv

2

我写这可能是用于本地命名空间中的任意一种shell内建或功能的POSIX外壳功能ksh93dashmksh,或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另一方面,常规内建函数(例如)被指定为在subshel​​l环境中运行-不一定意味着另一个进程,只是它与一个进程基本没有区别。调用常规内建函数的结果应始终类似于从类似功能$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程序的常规内置程序,可以在subshel​​l环境中进行调用。因此,虽然当前的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
}

非常聪明!此处使用了类似的模式来实现相同的目的:github.com/stephane-chazelas/misc-scripts/blob/master/locvar.sh
Zac B
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.