座敷牢日誌

都落ちした元SEがソフトウェアやネット関連のことを書いています

シェル変数と環境変数の違い

問題になることがあまりないというか, シェルスクリプトのなかでみるとほとんど使い方が変わらないようにみえることが多いせいか, 考えることあまりないシェル変数と環境変数の違いの話.

自分なりの理解で整理してみた. 本記事で示すのはすべてBashでの例である.

シェル変数/環境変数の定義

どちらも変数として, シェルスクリプトのなかで値をやりとりするために使われることが多く, それぞれ次のように定義できる.

# シェル変数の定義と宣言
message="hello world."

# 環境変数の定義と宣言
MESSAGE="HELLO WORLD."
export MESSAGE

環境変数export MESSAGE="HELLO WORLD." としても定義できる. declare コマンドを使って定義することもできる.

シェル変数/環境変数はスコープが異なる

シェル変数と環境変数の違いは, スコープにある. スコープとは, 変数の有効範囲のこと.

  • シェル変数……変数が定義されたシェル/サブシェルからアクセスできる
  • 環境変数……変数が定義されたシェル/サブシェルと, 子プロセスからアクセスできる

簡単にいうと, こういうことになっている.

シェル/サブシェルからのアクセス

シェル/サブシェルからのアクセスについて. シェル変数も環境変数も, 定義されたシェルスクリプトのなかからであれば, 読み込むことができる. ちなみにサブシェルは ( ...) のなかの行や, パイプの後ろの行がサブシェル扱いになる.

サンプルスクリプトで例示しよう.

#!/usr/bin/env bash

# シェル変数の定義と宣言
message="hello world."

# 環境変数の定義と宣言
MESSAGE="HELLO WORLD."
export MESSAGE

# 同じシェルからのアクセス
printf "シェル変数: %s, 環境変数: %s (%s)\n" \
    "$message" \
    "$MESSAGE" \
    "同じシェルからのアクセス"

# サブシェルからのアクセス 1
(
printf "シェル変数: %s, 環境変数: %s (%s)\n" \
    "$message" \
    "$MESSAGE" \
    "サブシェルからのアクセス 1"
)

# サブシェルからのアクセス 2
echo 1 | printf "シェル変数: %s, 環境変数: %s (%s)\n" \
    "$message" \
    "$MESSAGE" \
    "サブシェルからのアクセス 2"

このスクリプトを実行すると, 次のように出力される.

シェル変数: hello world., 環境変数: HELLO WORLD. (同じシェルからのアクセス)
シェル変数: hello world., 環境変数: HELLO WORLD. (サブシェルからのアクセス 1)
シェル変数: hello world., 環境変数: HELLO WORLD. (サブシェルからのアクセス 2)

シェル変数も環境変数も, 同じシェルスクリプト内から読み込めることがわかる.

子プロセスからシェル変数にはアクセスできない

一方, 子プロセスからシェル変数へのアクセスはできない. 子プロセスというのは, シェルのなかから別のプログラムを実行することを指す.

ここでは, シェルスクリプト内から別のシェルスクリプト (bash) と, Pythonスクリプトを実行し, 呼び出し元で定義したシェル変数と環境変数へのアクセスを試みる例を示す.

#!/usr/bin/env bash

# シェル変数の定義と宣言
message="hello world."

# 環境変数の定義と宣言
MESSAGE="HELLO WORLD."
export MESSAGE

# 子プロセス (bash) からのアクセス
# (ヒアドキュメント内のシェルスクリプトのファイルを実行しているのと同じ)
bash <(cat <<EOF
printf "シェル変数: %s, 環境変数: %s (%s)\n" \
    "\$message" \
    "\$MESSAGE" \
    "子プロセス (bash) からのアクセス"
EOF
)

# 子プロセス (python) からのアクセス
# (ヒアドキュメント内のpythonスクリプトのファイルを実行しているのと同じ)
python <(cat <<EOF
import os
buffer = \
    "シェル変数: {message}, 環境変数: {MESSAGE} (子プロセス (python) からのアクセス)"
print(buffer.format(
message = os.environ.get("message") if "message" in os.environ else "",
MESSAGE = os.environ.get("MESSAGE") if "MESSAGE" in os.environ else ""))
EOF
)

実行結果は次のようになる.

シェル変数: , 環境変数: HELLO WORLD. (子プロセス (bash) からのアクセス)
シェル変数: , 環境変数: HELLO WORLD. (子プロセス (python) からのアクセス)

この結果から, 子プロセスからシェル変数を参照することはできないことがわかる.

(補足) サブシェル/子プロセスで変更した値は, 親プロセスから参照できない

ここまでの話で, シェル変数/環境変数の有効範囲が理解できると思う. ただ, シェル変数/環境変数の違いという話だったので触れなかったが, 変数の値を変えたいときには注意がいる.

たとえば, サブシェルや子プロセスから変数の値を変えた場合, その値は呼び出し元である親プロセス/シェルスクリプトからは参照できない.

シェルで定義した変数の値を, サブシェルで変更するとどうなるかで例示しよう. パイプの後ろがサブシェル扱いになることは先ほど述べた通りである.

#!/usr/bin/env bash

# シェル変数を定義
message="Hello world."

# サブシェルで値を変更し, 出力する
echo 1 | {
    message="bye bye."
    printf "message: %s (サブシェルでの出力)\n" "$message"
}

# シェルでの出力
printf "message: %s (シェルでの出力)\n" "$message"

このスクリプトの実行結果は次のようになる.

message: bye bye. (サブシェルでの出力)
message: Hello world. (シェルでの出力)

サブシェルのなかで変更したシェル変数の値は, サブシェルの外では反映されていないことがわかる. これは環境変数であっても, 子プロセスであっても同じである. だから, サブシェルの処理結果をサブシェルの外へ渡すときには, ちょっと注意が必要である.

まあ, 一般的なプログラミング言語の関数や関数内の変数スコープを考えると, そりゃそうだろうという話ではあるけどね.