Pysh シェルスクリプトをPythonで書く

Pysh はシェルスクリプトをPythonで記述するためのプログラムです。 Pysh を利用すると、コマンドの実行やリダイレクションやパイプといったシェルスクリプトの 便利な機能を Python スクリプトの中で簡単に利用できるようになり、 Python をシェルスクリプトの代わりとしてより利用しやすくなります。 Python 使いならばもはやシェルスクリプトを書く理由は何もありません(Windows以外)。

目次

特徴

Pysh では Python スクリプトの中に含まれている > で始まる行をシェルコマンドとして実行します。 それ以外の部分は通常の Python スクリプトとして処理されます。 例えば次のスクリプトはfrom00.txt, from01.txt, ..., from99.txt を to00.txt...to99.txt に移動します。

for i in xrange(100):
  index = "%02d" % i
  > mv from$index.txt to$index.txt

Pysh は次のような特徴を持ちます

  • > で始まる行をシェルコマンドとして実行できる。
  • シェルコマンドの中から Pythonの変数 がシェルスクリプトと同じように $var のように参照できる。
  • パイプやリダイレクションも利用可能
  • if, for文などはシェルコマンドの中では利用できない。プログラムの制御はPythonで行う。
  • フィルタ系組み込み関数 map, filter, reduce がコマンドとして利用できる。 grep, sed, awk を利用しなくても柔軟なフィルタリング・置換・変形が行える。
  • 組み込みコマンドを Python で書くことができる。
  • Pythonで書かれたコマンドは入出力をPythonオブジェクトとして扱うことができる。
  • またPythonオブジェクトのまま、出力を他のコマンドにパイプすることができる

使い方

インストール

GitHubからソースコード をチェックアウトします。 Pysh は Python で実装されているので Python がインストールされている環境ならば 簡単に実行できます。ただしWindowsはサポート外です。 pysh はスクリプトファイルをファイル・標準入力・引数のいずれかで与えて実行することができます。

git checkout https://github.com/yunabe/pysh.git
./pysh/bin/pysh</pre>

ファイルから実行

python コマンドと同様に、pyshの引数としてファイルを与えるとファイルをpyshスクリプトとして 解釈し実行します。 pysh コマンドは与えられたファイルを通常の Python スクリプトに変換し Python として実行を行います。 そのため実行後、スクリプト名と同名の拡張子が .py のファイルが残ります。

pysh script.pysh

標準入力から実行

python コマンドと同様に、pyshの引数として - を与えると 標準入力からpyshスクリプトを読み込み実行します。 この機能はpyshをワンライナーの代わりに利用する際に重宝します。 複雑で読みづらいワンライナーを書くぐらいなら Python で構造化されたプログラムを書きましょう。 構造化されたスクリプトはワンライナーよりも遥かに読みやすく、将来再利用することができます。

pysh - << 'EOF'
for i in xrange(3):
  index = "%02d" % i
  > echo $index
EOF

引数から実行

python コマンドと同様に、pyshの引数として -c cmd を与えると cmd をスクリプトとして解釈、実行します。

pysh -c "`cat << 'EOF'
for i in xrange(3):
  index = "%02d" % i
  > echo $index
EOF`"</pre>

機能

変数参照

$var でPythonの変数をシェルコマンドの中から参照することができます。 環境変数も $PATH のように Python の変数と同じように参照できます。 また他のシェルと同様に "" で囲まれた文字列中の変数は自動的に展開されます。 '' で囲まれた文字列中の変数は展開されません。

x = 3
y = x * x
> echo $y     # 9
> echo $PATH  # os.environment['PATH']
> echo "$x * $x = $y"  # 3 * 3 = 9
> echo '$x * $x = $y'  # $x * $x = $y

Python式

Pyshでは変数だけでなく任意の Python 式をシェルコマンドの中で利用することができます。 シェルコマンドの中でPython式を書くには<code>${expr}</code>のように書きます。 変数参照と同様に、 "" 文字列中のPython式は評価されますが '' 文字列中のPython式は評価されません。

> echo ${3 + 4}  # 7
def f(x):
  return x * x
> echo ${f(10)}  # 100
> echo ${lambda x: x}

パイプ

Pysh ではその他のシェルと同じようにコマンドの入出力をパイプ | でつなぐことができます

> echo "foo\nbar" | tac

リダイレクション

Pysh ではリダイレクションを使ってコマンドの出力結果をファイルに出力することができます。 また、&>1 のようにして標準エラーの出力先を標準出力と同じにすることなどもできます。

> echo "Hello world" > /dev/null
> echo "Hello world" >> /dev/null
> echo "Hello world" 2>&1

&& || ;

Pyshでは他のシェルと同様に ; で複数のコマンドを順番に実行したり、 && で論理積(前者のコマンドが成功したら後者のコマンドを実行)や、 || で論理和(前者のコマンドが失敗したら後者のコマンドを実行)を使うことができます。

> echo foo; echo bar
> echo foo && echo bar
> echo foo || echo bar

文字列リテラル

Pysh ではシェルコマンド中の文字列リテラルの形式は Python のそれと同じです。 \n などのエスケープ文字もPythonと全く同じ物が利用できます。 Python に慣れていれば、シェル特有の文字列リテラルのルールを覚える必要はありません。 Pythonと同様に3つの "' で囲まれた範囲はヒアドキュメントとなります。 一方Pyshではその他のシェルと同様に、"で囲まれた文字列中の変数および式($var, ${expr})は 自動的に展開されます。

バッククオート

他のシェルと同じように、`` で囲まれたコマンドのパラメータに指定することで、 コマンドの実行結果を他のコマンドのパラメータとして利用することが可能です。

バックスラッシュ文字による行継続

Python や他のシェルと同じように、行の末尾にバックスラッシュ文字\ をつけると 改行が無視され次の行もつなげて1つの行として評価されます。

> echo foo\
    bar

コメント

Python と同様に Pysh では# より後ろの文字はコメントとして扱われます。

echo 'Hello world' # This is a comment

map filter reduce

ここからは他のシェルにはない pysh 独特の機能を紹介して行きましょう。 Pysh ではPythonのフィルタ系組み込み関数 map, filter, reduce に相当するコマンドが用意されています。

  • map は標準入力の各行に対して引数で指定された関数を適用し結果を標準出力に出力します。
  • filter は標準入力の各行に対して引数で指定された関数を適用し、関数が真を返した行のみを出力します。
  • reduce は標準入力の各行を、引数で指定された関数を使って1つの値に累積的にまとめます。
> seq 10 | map ${lambda s: int(s) * int(s)}
1
4
9
...
> seq 10 | filter ${lambda s: int(s) % 3 == 0}
3
6
9
> seq 10 | reduce ${lambda x, y: int(x) + int(y)}
55

map, filter, reduce をPythonの関数と組み合わせて利用することで grep, sed, awk などを使わなくてもフィルタリング・置換・変形を柔軟に実装できます。 Python にある程度慣れていればいちいち sed や awk の使い方を思い出すよりも効率的に 置換/変形処理が実装できます。

# The lambda expr of reduce does not need to str() to convert input.
> seq 10 | map ${lambda s: int(s) * 2} | reduce ${lambda x, y: x + y}
110

リターンコードの保存

pysh ではコマンドに -> をつけるとそのコマンドのリターンコードが -> の後ろに指定された変数に保存されます。 他のシェルではリターンコードは $? に保存されてしまうため、 パイプされているコマンドのそれぞれのリターンコードを参照することは困難ですが、 pysh ではパイプされているコマンドそれぞれに -> をつけることができるので、 簡単にそれぞれのコマンドのリターンコードを参照できます。 &&|| と共に利用され、-> が指定されたコマンドの実行が行われなかった場合には、Noneが保存されます。

> (python -c 'import sys;sys.exit(2)' -> rc0\
    || python -c 'import sys;sys;exit(0)' -> rc1)
> print rc0, rc1  # 512 0

Pythonへのリダイレクション

pysh ではコマンドの出力をファイルだけではなく Python の変数に対してリダイレクトすることが可能です。 Python 変数に対してリダイレクトするには => を使います。 コマンドの出力が各行が各要素として格納されたリストに変換され、Pythonの変数として保存されます。 -> と同様にコマンドが実行されなかった場合には None が保存されます。

> seq 5 | grep -v 3 => out
print out  # ['1', '2', '4', '5']
> send ${xrange(5)} | filter ${lambda i: i != 3} => out
print out  # [0, 1, 2, 4]

echo

=> とは逆に Pytnon 上のデータをシェルコマンドで利用するには echo コマンドを利用します。 pysh の echo コマンドはその他のシェルと同様に引数をスペースで区切った文字列を出力しますが、 引数にリストやタプルなどの文字列以外の iterable なオブジェクトが渡された場合には、リスト・タプルの各要素を一行づつ分けて出力します。

> echo foo bar  # foo bar
data = ['foo', 'bar']
> echo $data  # foo
              # bar
> echo $data | sort # bar
                    # foo

組み込みコマンド をPythonで実装する

(to be continued....)

最終更新: 7/25/2014