Code
from IPython import InteractiveShell
= 'all'
InteractiveShell.ast_node_interactivity
import session_info
August 30, 2024
以下のポストではRでglue
パッケージを用いたがそれと同じように、今度はPythonを使ってBashスクリプトの生成を行う。
Rのglueパッケージを使って、他の言語の一部分だけ変更したスクリプトを生成する。
https://t-arae.blog/posts/2024/2024-08-18-glue-shell-script/
Rではglue
パッケージを使って行うことができる文字列補完は、 Pythonではビルトインの機能であるf-stringやstr.format()
を用いる。
フォーマット済み文字列リテラル(f-string)を作成するには、 文字列リテラルの前にf
をつける(例: f"{apple}"
)。 実行するとプレースホルダ({}
)内の式が評価されて、置換された文字列が得られる。
format
メソッド(str.format()
)テンプレートとなる文字列型(str
)オブジェクトのformat()
メソッドを使って、 補完を行うことができる。 位置引数(*args
)と名前付き引数(**kwagrs
)を使って、プレースホルダを置換できる。
f-stringとは異なりプレースホルダ内は式として評価される訳ではない。 式を書くとエラーになる。
vars()
, locals()
, globals()
)スコープ内の変数を辞書で返す関数にはvars()
の他にlocals()
とglobals()
がある。 各関数について関数内と関数外それぞれで実行した場合に、どの変数にアクセスできるかを確認してみる。
# "one", "two"というkeyが辞書になければ、そのkeyに"undefined"という値をセット
def set_ud(d):
for key in ["one", "two"]:
if key not in d:
d[key] = "undefined"
return d
t = "{0:<10}: one is {one}, two is {two}"
# global scopeの変数
one = 1
def check():
# local scopeの変数
two = 2
print(t.format("vars()", **set_ud(vars())))
print(t.format("locals()", **set_ud(locals())))
print(t.format("globals()", **set_ud(globals())))
print("@outside check()")
print(t.format("vars()", **set_ud(vars())))
print(t.format("locals()", **set_ud(locals())))
print(t.format("globals()", **set_ud(globals())))
print("@insite check()")
check()
@outside check()
vars() : one is 1, two is undefined
locals() : one is 1, two is undefined
globals() : one is 1, two is undefined
@insite check()
vars() : one is undefined, two is 2
locals() : one is undefined, two is 2
globals() : one is 1, two is undefined
関数内で呼び出すと、locals()
ではグローバル変数に、globals()
ではローカル変数にアクセスできない。
先のポストの例をPythonを使って実践してみよう。 str.format()
を使う場合は以下の様に書ける。
from pathlib import Path
# Bashスクリプトのヘッダ
header = r"""!/bin/bash
set -euC
echo "Process started!"
echo ""
"""
# Bashスクリプトのフッタ
footer = r"""echo "All finished!"
"""
# スクリプトのテンプレート
cmd_template = r"""echo "Processing: '{label}'"
cat {label}.csv | \
awk -F, 'NR > 1 {{
sum1 += $1
sum2 += $2
}} END {{
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}}'
echo ""
"""
# ディレクトリ内のCSVファイル名からファイル拡張子を除く文字列を抽出
labels = sorted([f.stem for f in Path(".").glob("*.csv")])
with open("temp.sh", mode="w") as f:
_ = f.write(header)
for label in labels:
_ = f.write(cmd_template.format(**locals()))
_ = f.write(footer)
temp.sh
!/bin/bash
set -euC
echo "Process started!"
echo ""
echo "Processing: 'setosa'"
cat setosa.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'versicolor'"
cat versicolor.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'virginica'"
cat virginica.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "All finished!"
f-stringは即時評価されるので、f-stringを返す関数を定義してループ内で評価する。 また、f-string単体で使用すると\
をエスケープしなければいけないので、 raw-stringと組み合わせてfr""
として使う。
cmd_template = lambda : fr"""echo "Processing: '{label}'"
cat {label}.csv | \
awk -F, 'NR > 1 {{
sum1 += $1
sum2 += $2
}} END {{
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}}'
echo ""
"""
with open("temp.sh", mode="w") as f:
_ = f.write(header)
for label in labels:
_ = f.write(cmd_template())
_ = f.write(footer)
temp.sh
!/bin/bash
set -euC
echo "Process started!"
echo ""
echo "Processing: 'setosa'"
cat setosa.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'versicolor'"
cat versicolor.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "Processing: 'virginica'"
cat virginica.csv | \
awk -F, 'NR > 1 {
sum1 += $1
sum2 += $2
} END {
print "Avg. Sepal.Length:", sum1/(NR-1)
print "Avg. Sepal.Width :", sum2/(NR-1)
}'
echo ""
echo "All finished!"
str.format()
よりは、f-stringの方がRのglue::glue()
に近い感じで使える気がする。