座敷牢日誌

都落ちした元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. (シェルでの出力)

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

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

mktempコマンドを活用しよう

mktemp コマンドは一時ファイルあるいはディレクトリを作成するコマンドです.

何か試したいときに, 邪魔にならないディレクトリに適当なディレクトリを作って, カレントディレクトリを変更して……. といった手順や管理を少し簡単にできるかもしれません.

僕の mktemp コマンドの活用方法を紹介します.

ホームディレクトリに tmp ディレクトリを作っておく

mktemp コマンドは, /tmp に一時ファイル/ディレクトリを作成します. それでもいいのですが, root所有のディレクトリ内で作業するのが, 精神衛生上よくない気がしているので, ホームディレクトリ下に tmp ディレクトリを作っています.

$ mkdir $HOME/tmp

mktempのエイリアスを作る

ホームディレクトリに作った tmp ディレクトリを使うように, mktemp コマンドのエイリアスを作っておきましょう.

$ alias mktemp='mktemp -p $HOME/tmp'

ログインシェルの設定ファイルへ追記しておくのがオススメです.

一時作業用のディレクトリを作る

個人的には, 一時ファイルをコマンドによって作りたい状況はほとんどないので, ディレクトリを使う手順を紹介します.

mktemp -d で一時ディレクトリが作成されます.

$ mktemp -d
/home/kosuke/tmp/tmp.bMPBAmdwfT
$

コマンド実行したあとに出力されているのが, 作成されたディレクトリのパスです. しかし, このあとカレントディレクトリを変更しないといけませんから, cd $(mktemp -d) のように実行するのがいいかと思います.

$ cd $(mktemp -d)
$ pwd
/home/kosuke/tmp/tmp.vBYTczzCbC
$

一時ディレクトリの作成とカレントディレクトリの変更を一度に行うことができます.

LinuxのログインシェルをPowerShellへ変更する

せっかくLinuxPowerShellが使えるようになったのですから, 活用したいものですね. 手始めにログインシェルをPowerShellに変えてみてはいかがでしょうか. chsh -s /usr/bin/powershell で変更できます.

user1@1093d1e33f26:~$ chsh -s /usr/bin/powershell
Password:
user1@1093d1e33f26:~$ exit
[root@1093d1e33f26 ~]#
[root@1093d1e33f26 ~]# su user1
PowerShell
Copyright (C) 2016 Microsoft Corporation. All rights reserved.
PS /home/user1>

この通り, PowerShellがログインシェルになりました.

Microsoft提供のDockerイメージで確認しました. うまくいかない場合は, /etc/shellsPowerShellのインストール先を確認するのがいいかと思います 1.

ちなみに私は自分の環境でPowerShellを普段使いしているわけではないので, あしからず……. Windowsbashを使いたいニーズがあるのは, CygwinMinGWや, Windows 10へのbash導入といったニュースを見ていればわかりますが, PowerShell on Linux はどうなんでしょうね. メリットは多くあるとは思いますが.


  1. GitHubにあるMicrosoftPowerShellリポジトリで配布しているdebなどのパッケージで導入しているなら, インストール先は /usr だと思います