それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

php のシェル呼出しで引数を後から決める、変数内の文字列展開をする方法

php でコマンドを実行するとき、コマンド組み立てが、美しくない

$src = "my-sample.jpg"
$dst = "out.png"
$command = "convert $src $dst" ;

文字列の展開タイミングがあるので、これをクラスにすると面倒くさいんだよね。

コマンドを扱うクラスを作ると処理が面倒

文字列展開を後でやろうとすると出来無い。

<?php

class MySample{

  public $command = 'convert  $src $dst'; //あとで文字列展開をしたい
  public function __construct(){ /*なにかしょり*/}
  public function convert (){
    $src = escapeshellarg($this->source_file_name());
    $dst = escapeshellarg($this->destination_file_name()));
    $command = "{$this->command}" // "convert $src $dst" ; を展開したいけど出来ない。。。
  }
}

こうなると、文字列展開を後でするには、コマンドをメソッド中で組み立てる必要があり、これがコマンド文字列をデバッグするのが面倒になる原因。

解決策1: putenv を使う (シェル呼び出し時はオススメ)

putenv で環境変数に直接php変数を渡してあげる。シェル呼び出し時に環境変数を利用することで、見通しがすっきるする

<?php

class MySample{

  public $command = 'convert  $src $dst'; //あとで文字列展開をしたい
  public function __construct(){ /*なにかしょり*/}
  public function convert (){
    $src = escapeshellarg($this->source_file_name());
    $dst = escapeshellarg($this->destination_file_name()));

    putenv("src=$src");
    putenv("dst=$dst");

    ## シェル実行時に環境変数が使われる
    shell_exec($this->command);

    putenv("src=''");
    putenv("dst=''");

  }
}

putenv で環境変数を渡すのが結構便利だった。

コマンドに記述ミスが有ると思ったら、$this->command の中身をそのまま取り出せば、コピペで簡単に再現でき、シェルで実行がとても楽になる。

動作チェック、再実行したい時も簡単に、シェル起動してコピペで済む。

$ > src=my_jpg dst=out.jpg  convert  $src $dst

シェル呼び出しに使うコマンドを動的に組み立てないので、シェルスクリプトで使ってるコマンドをそのまま使うことが出来る。

追記:環境変数で突っ込む前に escapeshellargs されたら上手く動作しないよ。変数に無事突っ込めた時点でエスケープ不要だろうしね。。

解決策2: sprintf を使う

関数sprintfを使えば少しだけ、すっきりする。

<?php

class MySample{

  public $command = 'convert  %s %s'; //あとで文字列展開をしたい
  public function __construct(){ /*なにかしょり*/}
  public function convert (){
    $src = escapeshellarg($this->source_file_name());
    $dst = escapeshellarg($this->destination_file_name()));
    $command = vsprintf( $this->command, [$src, $dst] );// sprinft で文字を埋め込み
  }
}

文字列として展開される引数を後から決られるので、すっきりする。でも %s がたくさんあってワケガワカラナイヨ。

あとコマンド文字列が何を意味しているのか。%sでは変数の意図がでもわかりづらくなる。

解決策3:evalする

eval すれば出来る。

<?php
class MySample{

  public $command = 'convert  $src $dst'; //あとで文字列展開をしたい
  public function __construct(){ /*なにかしょり*/}
  public function convert () {
    $src = escapeshellarg($this->src);
    $dst = escapeshellarg($this->dst);
    eval("\$command =\"{$this->command}\";");
    }
  }

eval を活用すれば、出来ることがわかる。でもエスケープが多い。eval の代わりに php://memory に書き出して include なども使えそう。

eval を変数展開に使うのは個人的には使いたくない。もしやるとしたら

<?php
function expand_string( $string, $assoc_array ){
  extract($assoc_array);
  eval("\$ret = \"$string\";");
  return $ret;
}

などと先に関数を定義しとかないとevalで何したいのか、一目でわかりづらくて困る。

解決策4:関数を作る

これもeval とあんまり変わらないんだけど、関数をつくて、コールすれば用を満たせる。

<?php
$command = 'convert $src $dst';
$f = create_function('$src,$dst',  "return \"{$command}\" ;" );
shell_exec(  $f( "src.jpg", "dst.jpg"  )) ;

関数を作るとあとで中身が評価されるため、使い回し出来そう

環境変数を渡すのが便利だった。

シェルで作ったコマンドを、.bash_history から持ってきて貼り付けるだけだったので楽でした。

文字列を展開する方法は他にもあるかな?

  • シェルに任せる
  • eval
  • 関数作成
  • sprintf する
  • include する

他にいい方法ないのかなぁ。