コマンド入力を自動化する(シェルスクリプト)

Ippei Kishida

Last-modified:2018/03/29 22:55:48.

計算コマンドは数時間〜数日かかるものがザラにあります。 と思ったら数分で終わることもあります。 計算がいつ終わるのかを適宜確認しながら仕事をするのは嫌なものです。 その次にどんなコマンドを打つのか先に決まっているのならば、 その指示をファイルに書き込んで、計算機の動作を先行入力しましょう。

1 単純な順次実行

まず最初の例として、 カレントディレクトリにある bench.Hg という計算ディレクトリを calc0, calc1, calc2 という名前にそれぞれコピーしたいとします。 これを実行するにはコマンドラインに以下を打ち込むことになる筈です。

cp -r bench.Hg calc0
cp -r bench.Hg calc1
cp -r bench.Hg calc2

さて、これをコマンド入力するのではなく、 スクリプトにしてみましょう。 copy.zsh にそのまま書いて保存しましょう。 ( copy.zsh という名前はただの例であって、別の名前でもいいです。) コマンドラインで以下のように実行してみましょう。

zsh copy.zsh

まさに copy0, copy1, copy2 という名前でコピーされた筈です。 ls calc? などで中身を確認しておきましょう。

次に、 この calc{0..2} という計算ディレクトリのそれぞれで vasp を実行しましょう。 この時打ち込むコマンドは以下のようになる筈です。

cd calc0
vasp
cd ../calc1
vasp
cd ../calc2
vasp

vasp のところは自分の環境で実行するときのコマンドをそのまま入れましょう。 またしても、これをそのまま runvasp.zsh という名前のファイルに書き込み、 zsh runvasp.zsh で実行しましょう。 数秒〜数分程度時間がかかることでしょうが、1コマンドで全ての計算が実行できた筈です。

cd でディレクトリを移動するときに、 2回目以降は ../ が必要という点に注意が必要です。 これは 1回目の cd でカレントディレクトリが変更されているからです。 vasp は計算ディレクトリに移動してから実行するため、 最初はカレントディレクトリから下りるけれども、 2つ目以降は一度親ディレクトリに上がってから次のディレクトリに下りる必要があります。

スクリプトファイルの名前は *.zsh でなくても構いません。 ただ、慣れないうちはそのスクリプトを実行する拡張子を付けておいた方が分かり易くて良いでしょう。

2 繰り返し

今はディレクトリが3つだったので、一々書いても手間はさほどでもありません。 ここでディレクトリが 1000 個あったらどうでしょうか。 ちょっとやりたくない仕事です。 繰り返しを使って楽に書きましょう。

for i in calc0 calc1 calc2
do
  cp -r bench.Hg $i
done

ちょっとマシになりましたね。 ディレクトリ名の情報が一箇所にあつまっているので変更が容易です。

2.1 変数を使う

vasp の実行の場合にはもう一手間必要です。 相対パスでディレクトリを指定よりも、絶対パスで指定した方が便利です。 環境変数 PWD にカレントディレクトリのパスが入っているので、 $PWD でこれを取り出して使います。 スクリプト中の cd コマンドで PWD の値が変化するので、 変化する前に DIR に入れて保存しておきます。 ループの中では DIR の値とループ変数 i を使って 計算ディレクトリ名を生成しているわけです。

DIR=$PWD
for i in calc0 calc1 calc2
do
  cd $DIR/$i
  vasp
done

2.2 連番生成

シェルの機能がそのまま使えるので、 zsh だと連番生成を使うと便利です。

DIR=$PWD
for i in calc{0..2}
do
  cd $DIR/$i
  vasp
done

ここで calc{000..999} のようにするだけで、 1000個の計算を順次実行するスクリプトができることになります。

2.3 ディレクトリ内のファイル情報を使う

「スクリプトの修正をするのが面倒だ、 サブディレクトリ全てで vasp を実行したいんだ!」 という要求には、こうでしょう。

DIR=$PWD
for i in *(/)
do
  cd $DIR/$i
  vasp
done

ワイルドカード * と ファイルの属性によるグロビング を使って実行したディレクトリにある全てのサブディレクトリが ここに入ります。

3 zsh を省略してコマンドライン先頭にスクリプト名を書けるようにする

「zsh runvasp.zsh」ではなく、「runvasp.zsh」で実行したい。 ここまで来ると、zsh の機能に依存した内容になっていますので、 zsh 以外で実行することはないわけです。 実行する処理系もスクリプト内で指定しておいた方が間違いも少なくなります。

これまでのスクリプトでは「zsh で実行する」という情報を OS に対してコマンドラインで教えていましたが、 この情報をスクリプト自身に含めてしまえばこれが可能になります。 これを書き込んだ新しいスクリプトファイルの中身が以下になります。

#! /usr/bin/zsh

DIR=$PWD
for i in *(/)
do
  cd $DIR/$i
  vasp
done

1行目の「#! /usr/bin/zsh」がスクリプトを実行するプログラムのパスになっています。 これを shebang (シェバン、シバン)と呼びます。 shebang は1行目でなくてはならず、2行目以降に書かれても shebang として認識されません。

実行パーミッションを与えておきましょう。

% chmod 755 runvasp.zsh

これで、スクリプトを置いてあるディレクトリから 以下のように打つと実行できる筈です。

./runvasp.zsh

3.1 コマンドパスの通っているディレクトリに入れる

別の計算を始めるときに、 毎回 runvasp.zsh をコピーして持ってくるのも面倒な話です。 そのスクリプトを何度も繰り返し使用するということでしたら、 コマンドとして実行できるようにしましょう。

コマンドとして実行できるようにするには、 シェルがコマンド検索するディレクトリに入れておく必要があります。 「echo $PATH」 で確認できますが、 一般ユーザでは ~/bin というディレクトリを作り、 ここをコマンドサーチパスに含めて置くのが無難です。

~/.zshenv の 末尾に以下のように書いてみましょう。

PATH="$PATH:$HOME/bin"

そして以下のように実行します。

% mkdir ~/bin
% cp runvasp.zsh ~/bin/runvasp
% source ~/.zshenv
% rehash

cp でのコピーついでに、拡張子の .zsh も省略しました。 私がコマンドを使うときには そのコマンドがどういう機能をするかを考えることが殆どで、 それが何で書かれているかを意識しないのでこうしています。 別に拡張子の .zsh を除かなくても全く問題ありません。

source は現在対話的に使用しているシェルで、 環境設定ファイルを再読み込みしろ、という命令です。 PATH を書き換えたのだからこれが必要です。

rehash はコマンドサーチパス内のコマンドを再読み込みしろ、という命令です。 zsh は起動したときのコマンド情報しか持っておらず、 ~/bin/runvasp はたった今追加されたのですから、 これを追加してやる必要があったわけです。

これで任意のディレクトリで以下の runvasp コマンドが使えるようになった筈です。

% runvasp 

4 より高級なプログラミング言語への移行

シェルスクリプトも頑張れば条件分岐や文字列操作ができ、 様々なことができます。 ですが、ちょっと複雑なことをするのだったら Ruby などの高級なスクリプト言語を学んだ方がラクでしょう。 シェルスクリプトでも文字列の加工や数値計算は不可能ではありませんが、 これに習熟する手間が大変で、 また実際のコードの作り難さもなかなかのものです。

シェルは、ユーザとの対話型インターフェイスとして コマンドラインで使うのがベースとしてあるため、 プログラミング言語としてはどうしても制約が生じてしまいます。