Quantcast
Channel: アシアルブログ
Viewing all articles
Browse latest Browse all 298

シェルスクリプトを始めよう part2

$
0
0
前回の投稿から半年近く空いてしまいましたが、今回はPart2を書いて行きたいと思います。

※2013/07/10追記
コメントにて a様 よりこの記事が多々間違っているとご指摘がありました。
確かに間違った情報を発信しており、大変申し訳無く思っております。
間違ったところは追記にて修正させて頂きました。
※追記終了

さて、前回例に上げたスクリプトの場合は
・mencoderが失敗した場合に続いてffmpegが実行される
・引数がない場合はエラーが起こる
などの問題がありました。

どうにかエラーを検知して、分岐処理をしたくなりますよね。
ということで、今回はコマンドの戻り値と条件分岐です。


0・本題に入る前に、前回頂いたコメントについて
コメントありがとうございます。
コメントで頂いた通り、envコマンドで書いた方が移植性は上がります。
  1. #!/usr/bin/env bash
  2. ...

envコマンドは環境変数一覧や環境変数を設定出来るコマンドですが、
このように、$PATHに設定されている場所の中からプログラムを探して実行する機能も持ちます。
上記の場合はbashという名前のプログラムを探して実行します。

※2013/07/10追記
envコマンドを利用すると、指定したシェルプログラム(shなど)に引数を渡せなくなるので、
もし引数を渡す必要がある場合には使用出来ません。
※追記終了

もちろん、envが /usr/bin/ にある保証も無いですが、bashが /bin/ にある可能性よりは高くなるはずです。
少なくともMacは /usr/bin/env は存在しましたが、bashは /opt/local 以下でした。
※2013/07/10追記
Macも/bin以下にbashは存在しているそうです。
検証不足で申し訳ございませんでした。
※追記終了
他のUnixではどうなるかは、手元に環境が無いのでわかりませんが、パスが違う可能性はあります。

さて、上記の例では一応移植性は上がりますが、実はかなり無駄な事しています。
それは・・・"bash"です。

bashも環境によってあるのか不明なので、使うなら sh を使ってください。
  1. #!/usr/bin/env sh
  2. ...
実は、シンボリックリンクでbashにリンクしているだけかもしれませんが、念のため。

では、本題へ参ります。


1・コマンドの戻り値

実行したコマンドは戻り値を持ちます。
一般的な言語の関数と同じです。

正常終了の場合は0が返ります。
それ以外の場合は、0以外の数値が返ります。

戻り値はその式自体から返ってきますが、その結果を直接変数に代入は出来ません。

具体例見てもらった方が早いと思うので、下記を見てください。

  1. test -f /bin/bash
  2. echo $?
testコマンドは、ファイルの存在や数値比較をして、真偽(0かそれ以外)を返すプログラムです。
-f オプションは、続くファイルの存在を調べます。

$?には直前のコマンドの戻り値が代入されます。
この値を調べることにより条件を分岐出来ます。

  1. test -f /bin/bash && echo OK
変数を使わない場合は &&(AND)や ||(OR)で繋ぐことも出来ます。
&&は左から実行して全てのコマンドが0なら0を返します。0以外の場合はそれ以降は実行せずに、その戻り値を返します。
||は左から実行して0が返れば0を返します。0以外の場合は、次のコマンドを実行します。

  1. grep hoge /etc/passwd && echo OK
grepはなにかにマッチすれば0を返しますので、こういう書き方も出来ます。

この章の要点: コマンド自体に戻り値が設定されており、演算子か$?で読み取ることが出来ることを覚えてください。

3・条件分岐

先ほどの&&や||だと複雑な条件を書く場合に苦労するので、if文を使います。

  1. if test -f /bin/bash; then
  2.     echo "OK"
  3. fi
先ほどのサンプルと同じことをif文でしてみました。
if は引数にとったコマンドを実行し、条件が真なら直後の then から fi までのコマンドを実行します。

  1. if test -f /bin/bash; then
  2.     echo "OK"
  3. else
  4.     echo "NG"
  5. fi
もちろん、elseも使えます。

test以外のコマンドの実行結果を判定する場合、
  1. grep hoge /etc/passwd >/dev/null 2>&1
  2. if test "0" -eq "$?"; then
  3.     echo "OK"
  4. fi
上記のように$?変数を比較するか、

  1. if grep hoge /etc/passwd >/dev/null 2>&1; then
  2.     echo "OK"
  3. fi
ifに直接入れても動作します。

※2013/07/10追記
grepは-qオプションををつけることにより、標準出力無しにできるので、
  1. if grep -q hoge /etc/passwd ]
と書いた方がより良くなります。

ただし、標準エラーは出るので、状況によっては -s オプション(標準エラー出力なし)も追加してもいいかもしれません。
※追記終了

testコマンドだと読みにくいと言う人は"["コマンドもあります。
実体は(ほぼ)testコマンドなので、
  1. if [ -f /bin/bash ]; then
  2.     echo "OK"
  3. fi
このように書けます。
testコマンドよりも直感的になりました。

わかりやすいですが、注意点が2点あります。
  1. if[ -f /bin/bash ]
上記のように続けて書くと"if["なんてコマンド無いエラーになりますので注意してください。
また、"["コマンドで始めた場合は、最後に"]"を書かないとエラーになるので注意してください。

複数条件を追加する場合は、
  1. if [ -f /bin/bash -a -f /bin/dash ]; then
  2.     echo "OK"
  3. fi
このよう-aなどの引数を追加することにより、複数条件に出来ます。
-a は AND条件、 -o はOR条件、! はNOT条件になります。

細かいことは man test もしくは、 man [ で見てください。

ちなみに、ただのコマンドなので
  1. if [ -f /bin/bash ] && [ -f /bin/dash ]; then
  2.     echo "OK"
  3. fi
こんなこともできますが、あまり見ないですね。
わかりやすく書ければいいと思います。

この章の要点: if は引数のコマンドを受け取って、戻り値により条件を分岐します。

4・で、前回のスクリプトを直してみます
  1. #!/usr/bin/env sh
  2. infile="$1"
  3. tmpfile='/tmp/tmpfile'
  4. outfile="$2"
  5. if [ ! -f "$infile" ]; then
  6.     echo '入力ファイルが存在しません'
  7. fi
  8. if [ ! -d `dirname "$outfile"` ]; then
  9.     echo '出力先ディレクトリがありません'
  10. fi
  11. mencoder -noodml -vf il,scale=800:600 -ovc lavc -lavcopts vcodec=ffvhuff:vstrict=-1 -fps 30000/1001 -mc 0.1 -oac pcm "$infile" -o "$tmpfile" && \
  12. ffmpeg -y -i "$tmpfile" -f mp4 -vcodec libx264 -fpre ~/libx264-hqts-2.ffpreset -aspect 16:9 -an -threads 8 -pass 1 "$outfile" && \
  13. ffmpeg -y -i "$tmpfile" -f mp4 -vcodec libx264 -fpre ~/libx264-hqts-2.ffpreset -aspect 16:9 -acodec libfaac -ac 2 -ar 48000 -ab 192k -threads 8 -pass 2 "$outfile" && \
  14. rm "$tmpfile"

testコマンドの、
-f はファイル存在確認、
-d はディレクトリ存在確認
です。

mencoderから先は、if文でエラーメッセージ出してもいいのですが、大抵プログラム本体がエラー吐くのでこちら側では、成功したら次へ行くだけにしました。
なお、スクリプト途中で改行する場合は、"\"で"改行文字"をエスケープすると、一行と解釈してくれます。

5・関数
実は、シェルスクリプトは関数を定義することが出来ます。

  1. function hoge {
  2.     echo hogehoge
  3.     echo $1
  4.     echo $@
  5. }
function 関数名 { ... }と言う書式で書くだけです。
引数はシェルスクリプトの引数と同じで"$@"や"$1"などでアクセス出来ます。
知らなくてもどうにかなりますが、知っていれば便利です。

なお、コンパイルされる際に解釈されるようで、実際に呼び出す行より後に定義しても正常に動作します。
※2013/07/10追記
コンパイルされません。呼び出す前に定義しないとエラーとなります。
※追記終了

6・以上、条件分岐と関数の解説でした。
これで便利なバッチ処理は出来るようになりました。
あとは、便利なスクリプトを自分で作ってみてください。

次回があるなら、自分が作ったスクリプトを例文として公開するかもしれません。
※2013/07/10追記
初心者である私が個人的に書いたただ仕様通りに動くだけのスクリプトを公開したところで、今回のように多数の間違った書き方をしている可能性が考えられます。
公開した結果、会社のブランドを傷つけるような事になるのは絶対に避けねばならないので、金輪際シェルスクリプトについて書かないことを宣言いたします。
今後は得意分野の記事を推敲して載せていきたいと思います。

最後になりましたが、ブログの読者の皆様、
間違った情報を掲載してしまい、大変申し訳ございませんでした。

そして、コメント頂いた a様
貴重なお時間を削ってこのような恥ずかしい駄文を読んでいただき、間違った箇所をご指摘いただき大変有り難うございました。
もし、ご指摘が無ければ会社のブランドを傷つけ続けるところでございました。
本当に有難う御座いました。
※追記終了

Viewing all articles
Browse latest Browse all 298

Trending Articles