シェルの名前空間


10

source名前空間へのシェルスクリプト、できればbashシェルスクリプトへの方法はありますが、この機能があり、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
説明をありがとう。私は答えが否定的であることを期待しています。通常のシェルプログラミングパラダイムは、変更を分離する場合、サブシェルで実行すること( easiest thing ever )です。しかし、それはあなたが望んでいることとは全く異なります。私はあなたが行うことができると思います( stuff in subshell; exec env ) | sed 's/^/namespace_/'し、eval親シェルでの結果が、それは厄介なのようなものです。
Celada、2015

3
はい。ゲットksh93。名前空間はその基本です。名前のすべてのタイプタイプも可能)は名前空間をサポートしています。bashちなみに、それは事実上すべての点ではるかに高速です。
mikeserv 2015

@mikeservよろしくお願いします。機能例を示すコード例を含む回答として追加していただければ、承諾します。
PSkocik

@michas関数のシンボルとエイリアスにも名前空間を付ける必要があります。env | sed ...変数setに対しては機能しますが、関数を取得するために行うことはできますが、検索と置換は問題になります-関数は相互に呼び出すことができるため、すべてのクロス呼び出しを接頭辞付きのクロス呼び出しに置き換える必要がありますが、関数定義コードの他の場所にある同じ単語で、呼び出しではありません。そのためには、正規表現だけでなくbashパーサーも必要です。それでも、関数がevalを介して相互に呼び出さない限り、機能します。
PSkocik

回答:


11

インストールさman kshれているシステムからksh93...

  • 名前空間
    • namespace変数を変更したり新しい変数を作成したりするコマンドのリストの一部として実行されるコマンドと関数は、名前の前に識別子が付けられた名前空間の名前を名前に持つ新しい変数を作成し.ます。名前がnameである変数が参照されると、最初にを使用して検索され.identifier.nameます。
    • 同様に、名前空間リストのコマンドで定義された関数は、名前空間の名前の前にを使用して作成されます.
    • 名前空間コマンドのリストにコマンドが含まれている場合、namespace作成される変数および関数の名前は、変数または関数の名前の前に識別子のリストが付いたもので構成され.ます。名前空間の外では、名前空間内で作成された変数または関数は、その前に名前空間名を付けることで参照できます。
    • デフォルトでは、開始する変数.shsh名前空間にあります。

そして、実証するために、ksh93シェルで割り当てられたすべての通常のシェル変数にデフォルトで提供される名前空間に適用される概念を次に示します。次の例では、シェル変数にdiscipline割り当てられた.getメソッドとして機能する関数を定義し$PS1ます。すべてのシェル変数は、基本的には、少なくとも、デフォルトで独自の名前空間を取得しgetsetappend、およびunset方法。次の関数を定義した後、変数$PS1がシェルで参照されるたびに、出力がdate画面の上部に表示されます...

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

()上記のコマンド置換にサブシェルがないことにも注意してください)

技術的には、名前空間専門分野まったく同じものではありません(特定の名前空間にグローバルまたはローカルに適用するように専門分野を定義できるため)が、これらは両方とも、の基本であるシェルデータタイプの概念化の一部であり、重要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 2015

2

私は、ローカルのいずれかでシェル組み込みまたは関数の名前空間に使用される可能性がありますPOSIXシェル関数書かれているksh93dashmksh、またはbash (私は個人的にこれらのすべてで動作するようにそれを確認しているので、具体的という名前を)。私がそれをテストしたシェルのうち、それはでの私の期待に応えることができなかっただけで、それがyashでまったく機能するとはまったく思っていませんでしたzsh。私はテストしませんでしたposhposhしばらく前から希望をあきらめ、しばらくはインストールしていません。多分それはposh... で動作します

私が仕様を読んだことで、基本的なユーティリティの指定された動作を利用しているため、POSIXであると言いますが、確かに、この点に関して仕様は曖昧であり、少なくとも1人が明らかに私に反対しています。一般的に私はこれに同意しませんでしたが、最終的に自分のエラーであることがわかりました。また、今回も仕様について間違っているかもしれませんが、さらに彼に質問したところ、彼は返答しませんでした。

しかし、先ほど述べたように、これは前述のシェルで確実に機能し、基本的には次のように機能します。

some_fn(){ x=3; echo "$x"; }
x=
x=local command eval some_fn
echo "${x:-empty}"

3
empty

commandコマンドは、基本的に利用可能なユーティリティと事前の一つとして指定されている$PATH「Dの組み込み関数。指定された関数の1つは、特別な組み込みユーティリティを呼び出すときに独自の環境でラップすることです。

{       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

...上記の両方のコマンドライン割り当ての動作は仕様により正しいです。両方のエラー条件の動作も正しく、実際には仕様からほぼ完全に複製されています。関数のコマンドラインまたは特別な組み込みコマンドの前に付けられた割り当ては、現在のシェル環境に影響を与えるように指定されます。同様に、リダイレクトエラーは、いずれかを指すと致命的と指定されます。commandこれらの場合の特別な組み込みの特別な扱いを抑制するために指定され、リダイレクトの場合は実際には仕様の例で示されています。

command一方、などの通常のビルトインは、サブシェル環境で実行するように指定されています。これは必ずしも別のプロセスのビルトインを意味するのではなく、基本的に1つと区別できないはずです。通常のビルトインを呼び出した結果は、同様の機能を持つ$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コマンドはシェル関数を呼び出すことができないため、通常のビルトインの場合のように、特別な処理を無効にするために使用することはできません。それもスペックだ。実際、仕様では、主なユーティリティはcommand別のコマンドのラッパーシェル関数内で使用して、関数を呼び出さないため、自己再帰なしで他のコマンドを呼び出すことができると述べています。このような:

cd(){ command cd -- "$1"; }

commandそこで使用しなかった場合、このcd関数はほぼ間違いなく自己再帰のためにsegfaultを実行します。

しかし、特別なビルトインcommandを呼び出すことができる通常のビルトインとして、サブシェル環境でそれを行うことができます。だから、現在のシェルの状態が現在のシェルにかもしれないスティック内で定義されている間-確かreadさん$var1$var2やった-コマンドライン定義の少なくとも結果はおそらくないはず...

簡単なコマンド

コマンド名が得られない場合、またはコマンド名が特殊な組み込み関数または関数である場合、変数の割り当ては現在の実行環境に影響します。それ以外の場合、変数の割り当てはコマンドの実行環境用にエクスポートされ、現在の実行環境に影響を与えません。

command通常のビルトインであり、特別なビルトインを直接呼び出すことができるかどうかは、コマンドライン定義に関して予期しない抜け穴のようなものであるかどうかはわかりませんが、少なくとも4つのシェルはすでに知っていますcommand名前空間を尊重すると述べた。

また、commandシェル関数を直接呼び出すことはできませんeval、デモどおりに呼び出すことができるため、間接的に呼び出すことができます。したがって、私はこの概念に名前空間ラッパーを構築しました。次のような引数のリストを取ります。

ns any=assignments or otherwise=valid names which are not a command then all of its args

...ただし、command上記の単語は、空で検出された場合にのみ認識されます$PATH。加えて、コマンドラインで指定されているシェル変数はローカルスコープ、それはまたのような単一小文字のアルファベットの名前および他の標準的なもののリストを持つすべての変数をローカルスコープ$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がラップターゲットまたはその引数を使用して、なんらかの方法で明示的に関数から抜け出すことを妨げるものは何もありません。そうすることで、他の方法で試行する状態の復元/クリーンアップを防ぐことができます。

それをすべて行うには、3 evalの深さに行く必要があります。まず自身をローカルスコープでラップし、次に、内部から引数を読み取り、有効なシェル名があるかどうかを検証し、そうでない場合はエラーで終了します。すべての引数が有効であり、最終的に1原因である場合はcommand -v "$1"trueを返すために(リコール:$PATHこの時点では空である)、それは意志evalのコマンドライン定義し、ラップターゲットに残りのすべての引数をオフに渡し、それがために、特別なケースを無視しても(ns-それwouldn理由それほど有用ではありません。3 eval秒間の深さで十分です)

基本的には次のように機能します。

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はここで非常に注意しようとするので、それは長いです-3 つは難しいです。しかし、それであなたはできる:

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      

そして、これが関数です。すべてのコマンドには\alias展開を避けるために接頭辞w /が付いています。

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.