Pythonでテキスト内の環境変数を展開するワンライナー

2022.10.24

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ドキュメントの中身などにある程度汎用性を持たせたい時、 環境変数を展開してくれるような仕組みがあると便利な時があります。 envsubstというコマンドがあり、

$ echo 'AAA${LANG}ZZZ' | envsubst
AAAja_JP.UTF-8ZZZ

のように標準入力を渡してあげると環境変数で展開した文字列を出力してくれます。 ちょっとした機能で便利なのですが、残念ながらMacなどの多くの環境では標準でインストールされていません。 自分だけが使うのであればインストールすればいい話ですが、 他の人も使う環境に仕込みたい時は色々面倒が増えてしまいます。

そこで少し調べてみたところ、Python3でos.path.expandvars()というメソッドがあることを知りました。 もちろんあらゆる環境にPython3が入っているとは言い切れませんが、 少なくとも私の周辺ではPython3が入っていることは前提として良いことが多いため、 今回はPython3を使ってワンライナーで同等のことをやってみました。

実行環境

  • MacOS X Monterey 12.5.1
  • bash-3.2
  • Python3.10で動作確認
    • 具体的に未確認ですが、expandvars自体は執筆時点の公式ドキュメントで確認できる一番古い3.5でも使用できるようです。
    • また2.7にも同じメソッドがあるようですが、こちらは同じワンライナーで動くかは不明です(未検証)。

Pythonで書いてみる

expandvarsのインタフェースとしては

def expandvars(s: str) -> str

なので、文字列を受けとったものを環境変数展開して文字列で返すというシンプルなものです。

コマンドラインで使ってみる

pythonコマンドのオプション-cに続いてスクリプトを直接指定してワンライナーを書いていきます。 実際に環境変数に展開可能な部分を含んだ文字列を渡すとこんな感じになります。

$ echo 'AAA${LANG}ZZZ' | python -c 'import os, sys; print("".join([os.path.expandvars(l) for l in sys.stdin.readlines()]))'
AAAja_JP.UTF-8ZZZ

環境変数を{ }で囲うことは必須ではありませんが、この例のように、 後続にすぐアルファベットが続くような場面では必須となりますので、必ずつけると考えた方が間違いは少ないと思います。

ポイントとしてはリスト内包表現を使っているところと、 標準入力を受け付けるのにsys.stdinを使用しているくらいでしょうか。 長さも、ワンライナーとしてはまあ許容範囲かと思います。

あ、あとなんといっても、標準ライブラリ以外使っていないという点も大事ですね。 もし何かしらのインストールの手間が必要だったらenvsubstインストールしてもらった方がいいですし。

注意点

存在しない環境変数名を指定した場合、(bashのように)空文字ではなく、展開前の文字列がそのまま出力されます。

$ echo 'AAA${HOGE}ZZZ' | python -c 'import os, sys; print("".join([os.path.expandvars(l) for l in sys.stdin.readlines()]))'
AAA${HOGE}ZZZ

これはenvsubstの挙動とも違う点なので、移植する時などは一応注意が必要です。

$ echo 'AAA${HOGE}ZZZ' | envsubst
AAAZZZ

とはいえ、未定義の変数を空文字で使いたいという場面はあまりないかと思いますので、 ひとまずは大きな問題ではないかなぁと思っています。 ただ、WARNINGを出してくれたりするようなオプションもあったら嬉しかったんですけどね。。

また、未定義時のデフォルト指定${HOGE-xxx}なども対応はしていないようです。

$ echo 'AAA${HOGE-xxx}ZZZ' | python -c 'import os, sys; print("".join([os.path.expandvars(l) for l in sys.stdin.readlines()]))'
AAA${HOGE-xxx}ZZZ

なお、こちらはenvsubstも同様です。

$ echo 'AAA${HOGE-xxx}ZZZ' | envsubst
AAA${HOGE-xxx}ZZZ

余談

ワンライナーで書く場合はfor文は使えないようです。 ワンライナーの場合;で改行を表現できますが、forがあると invalid syntax と言われてしまいした。

$ python -c 'import os, sys; for l in sys.stdin.readlines(): print(os.path.expandvars(l))'
  File "<string>", line 1
    import os, sys; for l in sys.stdin.readlines(): print(os.path.expandvars(l))
                    ^^^
SyntaxError: invalid syntax

大抵の場合は今回のようなリスト内包表現で代用できるかと思います。

余談の余談

for文の直前の;を無理矢理改行コードにしてやればfor文でも動きました。 もちろん使うことはオススメしません!

$ echo 'AAA${LANG}ZZZ' | python -c 'import os, sys^Mfor l in sys.stdin.readlines(): print(os.path.expandvars(l))'
AAAja_JP.UTF-8ZZZ

^Mはターミナルで ctrl-vを押した後にEnterキーを押すと入力できます。

まとめ

envsubstはインストールしないと使えないので、Python3の標準ライブラリで代用することができました。 ワンライナーとしてはちょっと長いけど、そこまで気にならない長さに収まっています。 未定義の環境変数は空文字ではなく、展開されない文字列のまま出力される点には少し注意が必要です。

みんなに配布するシェルスクリプトの中の小技として活用できそうです。