シェルネームスペース

シェルネームスペース

シェルスクリプトを名前空間(好ましくはbashシェルスクリプト)に入れる方法はありますかsource?しかし、他のシェルにこの機能があり、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

man kshシステムで...ksh93

  • 名前空間
    • 変数を変更したり新しい変数を生成したりするコマンドのリストの一部として実行されるコマンドと関数は、名前が古い識別子によって指定された名前空間の名前であるnamespace新しい変数を生成します。.。 nameという変数を参照するときは、まず次のようにしてください。.identifier.name
    • 同様に、名前空間リストのコマンドで定義された関数は、で始まる名前空間名を使用して生成されます.
    • 名前空間コマンドリストにnamespaceコマンドが含まれている場合、生成された変数と関数の名前は変数または関数名で構成され、各名前の前には識別子のリストが続きます.。名前空間の外側では、名前空間名の前に名前を追加して、名前空間内で生成された変数または関数を参照できます。
    • デフォルトでは、.sh次から始まる変数はsh名前空間。

そして、説明のためにksh93シェルに割り当てられているすべての一般的なシェル変数に対してデフォルトで提供される名前空間に適用される概念は次のとおりです。次の例ではdiscipline.getこの関数はシェル変数に指定されたメソッドとして機能します$PS1。各シェル変数には、デフォルトで少なくともデフォルトgetsetおよびメソッドをappend含む独自の名前空間があります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!

答え2

ksh93dashまたはmkshいずれかでシェル組み込みまたは関数をローカルにネームスペースに使用できるPOSIXシェル関数を作成しました。bash (私が個人的にすべての製品に効果があることを確認したので、特に名前を付けました)。私がテストしたシェルでは私の期待に満たなかっただけで、うまくいくとyashは全く期待できませんでしたzsh。私はそれをテストしていませんposh。私は以前に希望をあきらめ、poshしばらくインストールしませんでした。おそらくposh...?

仕様を読んだ結果、デフォルトユーティリティの指定された動作を使用するため、POSIXと言います。しかし、明らかにこの点では、仕様はあいまいであり、少なくとも一人明らかに私の意見に同意しません。一般的に言えば、私はこの問題について問題を提起し、最終的にエラーが私自身のものであり、おそらく今回も仕様で間違っていることに気づきましたが、私が彼にもっと尋ねたとき、彼は答えませんでした。

しかし、前述したように、これは上記のシェルで動作し、デフォルトでは次のように動作します。

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

3
empty

このcommandコマンドは、利用可能な基本ユーティリティと事前組み込みコマンドのいずれかで指定されます$PATH。指定された機能の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

...仕様によると、上記の2つのコマンドライン割り当ては正しく機能します。両方のエラー条件の動作も正確であり、実際には仕様とほぼ正確に同じです。指定された関数または特殊な組み込み関数にコマンドラインプレフィックスを割り当てると、現在のシェル環境に影響します。同様に、リダイレクトエラーは、これらのエラーを指すときに致命的なエラーとして指定されます。commandそのような場合、特殊な組み込み機能の特殊な処理を抑制するように指定されており、リダイレクトのケースは実際には仕様の例で説明されています。

command一方、一般的な組み込み関数(例:)は次のように指定されます。サブシェル環境- 必ずしもその意味ではない。他のプロセスただし、根本的に一つと区別することはできません。一般的な組み込み関数を呼び出した結果は、常に同様の機能の 'd コマンドで$PATH得られた結果と似ている必要があります。だから...

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ほぼ確実に自己再帰セグメントが発生します。

ただし、特殊な組み込み関数を呼び出すことができる一般的な組み込み関数としては、次の目的で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上記の単語は、nullが見つかった場合にのみ1つとして認識されます$PATH。コマンドラインで指定されたローカルスコープのシェル変数に加えて、単一の小文字の名前と、、、、、、、および他のいくつかの$PS3その他の標準名のリストを含むすべての変数のスコープをローカルに指定します。$PS4$OPTARG$OPTIND$IFS$PATH$PWD$OLDPWD

はい、合計変数の範囲をローカルに指定し、明示的に範囲を指定して、$PWD現在の作業ディレクトリの範囲をかなり安定して決定することもできます。非常に頑張っていますが、これに対する保証はありません。ディスクリプタを保持し、ラップ先が返されたときにディスクリプタを保持します。現在の作業ディレクトリがその間に変更された場合でも、少なくとも元の作業ディレクトリに戻すことはできますが、この場合は見苦しいエラーが発生します。そして記述子を保持するので、通常のカーネルがルートデバイスのマウント解除を許可してはいけないと思います。$OLDPWDcd$OLDPWD$PWD7<.cd -P /dev/fd/7/unlink()(???)

また、ローカルスコープでシェルオプションを設定し、これらのオプションをラップするユーティリティが返されたときに検出された状態に復元します。特別な$OPTS処理は、独自の範囲内でコピーを維持し、最初にコピーに値を割り当てることです$-set -$OPTSラップターゲットを呼び出す前に、コマンドラインのすべての割り当てが処理された後にこれを行います。この方法で-$OPTSコマンドラインで定義した場合は、ラップ先のシェルオプションを定義できます。ターゲットが戻ると、自分set +$- -$OPTSのコピーも一緒にインポートされます。$OPTS (コマンドライン定義の影響を受けません)すべてを元の状態に復元します。

returrnもちろん、ターゲットまたはその引数をラップして、呼び出し側が何らかの方法で関数を明示的に終了するのを防ぐことはできません。これにより、状態の復元/クリーンアップを試みることができなくなります。

そのためには、3つの心を深く掘り下げなければなりませんeval。まず、ローカルスコープで自分自身をラップし、内部でパラメータを読み取り、そのパラメータが有効であることを確認してから、無効なシェル名が見つかった場合はエラーで終了します。すべてのパラメータが有効で、最後に1つのパラメータがある場合はcommand -v "$1"trueを返します。(注:$PATH現在は空です)evalコマンドライン定義を取り、残りのすべての引数をラッパーターゲットに渡します。(-の特別な場合は無視するがあまりns役に立たず、3sevalはすでに十分に深いので)

デフォルトでは、次のように動作します。

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 (???)しかし、これはすべて補助的であり、主に望ましくない出力と極端な場合に同様の出力を放出するのを防ぐために使用されます。これがうまくいく方法です。ネストされたユーティリティを呼び出す前に独自のローカル範囲を設定するため、これらの操作を実行できます。

ここではとても気をつけなければならないので長いです。 3つ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      

これが機能です。拡張を\避けるために、すべてのコマンドにプレフィックスが付いています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
}

関連情報