Zsh (と Python) の小ネタです。
事件
ある日、Windows 環境で使われている Python スクリプトを受け取りました。しかし手元の Ubuntu で実行したところ動作せず。トレースバックによるとスクリプトの実行ユーザー名を取得する処理で環境変数 USERNAME を参照、KeyError が発生していました。
Traceback (most recent call last):
[...]
return os.environ["USERNAME"]
File "/usr/lib/python2.7/UserDict.py", line 23, in __getitem__
raise KeyError(key)
KeyError: 'USERNAME'
たしか Unix で実行ユーザー名がセットされるのは環境変数 USER でなかったか? Zsh で確認してみよう。
$ echo "<$USER>"
<taro>
$ echo "<$USERNAME>"
<taro>
おやおや、今時の Unix では USERNAME も定義されているのか。ではなぜ Python スクリプトから参照できないのか。
調査
チクチクチク ポーン💡
判明
わかりました。$USERNAME は Zsh 固有のパラメータでした。Zsh から参照できますが子プロセスには渡りません。
$ dash -c 'echo "<$USERNAME>"'
<>
$ bash -c 'echo "<$USERNAME>"'
<>
$ zsh -c 'echo "<$USERNAME>"'
<taro>
man zshparam で zsh のマニュアルを見ると詳細が書いてあります。
USERNAME <S>
The username corresponding to the real user ID of the shell
process. If you have sufficient privileges, you may change the
username (and also the user ID and group ID) of the shell by
assigning to this parameter. Also (assuming sufficient privi-
leges), you may start a single command under a different user-
name (and user ID and group ID) by `(USERNAME=username; com-
mand)'
教訓
環境変数 USERNAME は Windows で有効ですが Unix ではの利用は控えましょう。代わりに環境変数 USER を参照しましょう。
おまけ
そもそも Python で実行ユーザー名を取得するときは環境変数ではなく getpass モジュールの getuser() 関数を使いましょう。ソースコードを見ると環境変数の LOGNAME, USER, LNAME, USERNAME を順に参照していることがわかります。(LNAME って何だ?)
/usr/lib/python/getpass.py より抜粋:
def getuser():
"""Get the username from the environment or password database.
First try various environment variables, then the password
database. This works on Windows as long as USERNAME is set.
"""
import os
for name in ('LOGNAME', 'USER', 'LNAME', 'USERNAME'):
user = os.environ.get(name)
if user:
return user
# If this fails, the exception will "explain" why
import pwd
return pwd.getpwuid(os.getuid())[0]
まとめ
実行ユーザー名を取得する場合、
- Windows なら環境変数 USERNAME を参照する。
- Unix なら環境変数 USER を参照する。
- Python なら getpass.getuser() を使う。