シェル変数にはどのようなスコープがありますか?


42

シェル変数のスコープが明確ではないことを示す問題に遭遇しました。

を使用しようとしていましたbundle install。これは、値を使用$GEM_HOMEして作業を行うRubyコマンドです。私は設定$GEM_HOMEしましたが、コマンドは、使用するまでその値を無視しexportましたexport GEM_HOME=/some/path

これにより、変数が何らかの形で「グローバル」(環境変数とも呼ばれます)になることを読みましたが、その意味がわかりません。私はプログラミングのグローバルについては知っていますが、異なるプログラムについては知りません。

また、このような変数の設定が現在のシェルセッションにのみ適用される場合、たとえばデーモン化されたプロセスに対してどのように設定しますか?

シェル変数にはどのようなスコープがありますか?

回答:


33

プロセスはツリーとして編成されています。すべてのプロセスが離れてからユニークな親、持っていinitPID常に1で、親を持ちませんが。

通常、新しいプロセスの作成は、ペアのfork/ execvシステムコールを介して行われます。ここで、子プロセスの環境は親プロセスのコピーです。

シェルから環境に変数を配置するには、exportその変数に対して行う必要があるため、すべての子に再帰的に表示されます。ただし、子が変数の値を変更すると、変更された値はその変数とその変更後に作成されすべてのプロセス(前述のようにコピー)にのみ表示されることに注意してください。

子プロセスが環境を変更する可能性があることも考慮してくださいlogin。たとえば、おそらくそれがデフォルト値にリセットされる可能性があります。


1
あ!OK、これが理解できるかどうか見てみましょう。シェルでは、と言うとFOO=bar、現在のシェルプロセスの値を設定します。次に(bundle install)のようなプログラムを実行すると、子プロセスが作成されますが、子プロセスはにアクセスできませんFOO。しかし、私が言っていた場合export FOO=bar、子プロセス(およびその子孫)それにアクセスできます。そのうちの1つはexport FOO=buzz、その子孫の値を変更するために、または単にFOO=buzz自分自身の値を変更するために呼び出すことができます。それは正しいですか?
ネイサンロング

2
@NathanLongそれは正確ではありません:現代のすべてのシェルでは、変数はエクスポートされます(したがって、値の変更は子孫の環境に反映されます)またはエクスポートされません(変数が環境にないことを意味します)。特に、シェルの起動時に変数がすでに環境にある場合、エクスポートされます。
ジル 'SO-悪であるのをやめる'

2
「子が変数の値を変更した場合、変更された値は変数とその変更後に作成されたすべてのプロセスにのみ表示されます」という文に少し混乱しました。「...それと、その変更後に作成されたすべての子孫プロセスから見える」と言う方が正しいでしょう。親プロセスの他の子は、子プロセスの後に開始された子も影響を受けません。
ジャアン

26

少なくともkshandおよびunder bashでは、変数は3つのスコープを持つことができますが、残りのすべての答えが現在伝えているような2 つのスコープはありません。

エクスポートされた(つまり環境)変数とシェルのエクスポートされていない変数スコープに加えて、関数ローカル変数用の3番目に狭いスコープもあります。

typesetトークンを使用してシェル関数で宣言された変数は、そこから呼び出された(サブ)関数で宣言された関数内でのみ表示されます。

このksh/ bashコード:

# Create a shell script named /tmp/show that displays the scoped variables values.    
echo 'echo [$environment] [$shell] [$local]' > /tmp/show
chmod +x /tmp/show

# Function local variable declaration
function f
{
    typeset local=three
    echo "in function":
    . /tmp/show 
}

# Global variable declaration
export environment=one

# Unexported (i.e. local) variable declaration
shell=two

# Call the function that creates a function local variable and
# display all three variable values from inside the function
f

# Display the three values from outside the function
echo "in shell":
. /tmp/show 

# Display the same values from a subshell
echo "in subshell":
/tmp/show

# Display the same values from a disconnected shell (simulated here by a clean environment start)
echo "in other shell"
env -i /tmp/show 

この出力を生成します:

in function:
[one] [two] [three]
in shell:
[one] [two] []
in subshell:
[one] [] []
in other shell
[] [] []

ご覧のとおり、エクスポートされた変数は最初の3つの場所から表示され、エクスポートされていない変数は現在のシェルの外部には表示されず、関数ローカル変数は関数自体の外部に値を持ちません。最後のテストでは値がまったく表示されません。これは、エクスポートされた変数がシェル間で共有されないためです。つまり、継承された変数のみが継承され、継承された値は親シェルによる影響を受けません

この後者の動作は、完全にグローバルですべてのプロセスで共有されるシステム変数を使用できるWindowsの動作とはまったく異なることに注意してください。


12

プロセスごとにスコープが設定されます

他の回答者は、シェル変数スコープがプロセスとその子孫に関するものであることを理解するのに役立ちました。

lsコマンドラインのようにコマンドを入力すると、実際にはプロセスをフォークしてlsプログラムを実行します。新しいプロセスには、親としてシェルがあります。

すべてのプロセスは、子プロセスに渡されない独自の「ローカル」変数を持つことができます。また、「環境」変数を設定することもできます。を使用exportすると、環境変数が作成されます。いずれの場合も、無関係なプロセス(元のプロセスのピア)には変数が表示されません。子プロセスが見るものを制御するだけです。

Aと呼ぶbashbashシェルがあるとします。これを入力するとexport、Bと呼ぶ子プロセスbashシェルが作成されます。Aで呼び出したものはすべてBに設定されます。

さて、Bでは、と言いFOO=bます。次の2つのいずれかが発生します。

  • Bが(Aから)と呼ばれる環境変数を受け取らなかった場合FOO、ローカル変数を作成します。Bの子はそれを取得しません(Bが呼び出さない限りexport)。
  • B (Aから)呼び出された環境変数を受け取った場合、B 自体と、その後分岐したchildrenのためにそれFOO変更します。Bの子には、Bが割り当てた値が表示されます。ただし、これはAにはまったく影響しません。

ここに簡単なデモがあります。

FOO=a      # set "local" environment variable
echo $FOO  # 'a'
bash       # forks a child process for the new shell
echo $FOO  # not set
exit       # return to original shell
echo $FOO  # still 'a'

export FOO # make FOO an environment variable
bash       # fork a new "child" shell
echo $FOO  # outputs 'a'
FOO=b      # modifies environment (not local) variable
bash       # fork "grandchild" shell
echo $FOO  # outputs 'b'
exit       # back to child shell
exit       # back to original shell
echo $FOO  # outputs 'a'

これらすべてが私の元の問題を説明しています。GEM_HOMEシェルに設定しましたが、を呼び出すとbundle install、子プロセスが作成されました。私は使用していなかったためexport、子プロセスはシェルのを受け取りませんでしたGEM_HOME

エクスポート解除

を使用して、変数を「エクスポート解除」できます-変数が子に渡されないようにしexport -n FOOます。

export FOO=a   # Set environment variable
bash           # fork a shell
echo $FOO      # outputs 'a'
export -n FOO  # remove environment var for children
bash           # fork a shell
echo $FOO      # Not set
exit           # back up a level
echo $FOO      # outputs 'a' - still a local variable

1
「それ自体とその子に対して変更します」と言うときは、変更に作成され子のみが変更された値を見ることになります。
-enzotib

1
@enzotib-良い点。更新しました。
ネイサンロング

3

エクスポートについて見つけることができる最良の説明はこれです:

http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_02.html

サブシェルまたは子シェル内の変数セットは、それが定義されているサブシェルにのみ表示されます。エクスポートされた変数は、実際には環境変数になります。明確にbundle installするために、独自のシェルを実行します。シェル$GEM_HOMEは、environment変数としてエクスポートされる場合を除き、表示されません。

ここで変数スコープのドキュメントを見ることができます:

http://www.tldp.org/LDP/abs/html/subshel​​ls.html


ああ、だから、「環境変数」という用語を使用するのは間違っていましたFOO=bar。使用exportする必要があります。質問はそれに応じて修正されました。
ネイサンロング

私が追加したリンクを見てみましょう。
Karlson

3

予想どおり、変数スコープの階層があります。

環境

最も外側のスコープは環境です。これは、オペレーティングシステムによって管理される唯一のスコープであるため、すべてのプロセスに存在することが保証されています。プロセスが開始されると、プロセスは親の環境のコピーを受け取ります。その後、2つのプロセスは独立します。子の環境を変更しても親の環境は変更されず、親の環境を変更しても既存の子の環境は変更されません。

シェル変数

シェルには独自の変数の概念があります。これは、物事が少し混乱し始める場所です。

シェル内の変数に値を割り当て、その変数が既に環境に存在する場合、環境変数は新しい値を受け取ります。ただし、変数がまだ環境にない場合は、シェル変数になります。Ruby変数がRubyスクリプト内にのみ存在するのと同様に、シェル変数はシェルプロセス内にのみ存在します。子プロセスに継承されることはありません。

ここでexportキーワードが役立ちます。シェル変数をシェルプロセスの環境にコピーして、子プロセスが継承できるようにします。

ローカル変数

ローカル変数は、それらを含むコードブロックをスコープとするシェル変数です。typesetキーワード(portable)またはlocalor declare(Bash)を使用してローカル変数を宣言します。他のシェル変数と同様に、ローカル変数は子プロセスに継承されません。また、ローカル変数はエクスポートできません。

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