2015年11月10日火曜日

JavaのRuntime.exec(String cmd)をスペース対応する

ITFFFとかいうサービスを使ってみようと思い、ブログに投稿するとFacebookにも自動で投稿するように設定してみました。
なので久しぶりに技術ブログを更新。


JavaのRuntime.exec(String cmd)メソッドはとっても便利なのですが、cmd文字列の中のファイル名やコマンドオプション文字列にスペースが入っている場合に動作がおかしくなります。



cmd="ls *.bat";

たとえば、上記のパラメーターでRuntime.exec()を呼び出すときちんと動きます。

cmd="sh -c \"ls *.bat\"";

しかし、上記のパラメーターのようにコマンド文字列に'"'でくくった部分がある場合、Runtime.exec()を呼び出すとエラーになてしまい、きちんと動きません。
C言語とか他の言語では動くのに。

ネットの一部の掲示板ではshコマンドのパスが悪いとかいろいろ書かれていますが、どれも原因がいまいち。しかもWindowsではきちんと動くのですが、MacやLinuxでは動かないのです。
そこで、動かない原因を解析しました。


JAVAの仕様書を見ると、Runtime.exec(String cmd)はStringTokenizerを使ってRuntime.exec(String[] cmd)に分解されると書かれています。
StringTokenizerは上記の文字列を以下のように分解するようです。

String[] cmd={"sh","-c","\"ls","*.bat\""};

StringTokenizerはスペースがくるとそこで文字列を強制分割してしまうので、'"'でくくった部分を正しく解釈してくれないようです。

似たようなメソッドで文字列の配列を渡すRuntime.exec(String[] cmd)メソッドならばスペースがあっても大丈夫なのですが、Runtime.exec(String cmd)メソッドはスペース対応してないので、仕様書を見る限り、正しく動作しないようです。

Runtime.exec()メソッドにStringの配列で渡せばよいのですが、関数の戻り値とかをRuntime.exec()メソッドに渡す場合、配列だといろいろめんどくさいしなぁ。
他の言語ではだいたいどれもできるのに、Javaだけできないなんて。うーん。困った。


そこで今日一日よーく考えて、StringTokenizerの代わりとなる、execTokenizerメソッドを作ってみました。

これがあれば、こんな感じでスペースやリダイレクトがある文字列でも正しく動きます。

Runtime.exec(execTokenizer("sh -c \"ls *.bat\""))

急いで書いたのでexecTokenizer()がまだ汚い。
原理は確認できたのであとでソースをきれいにしよう。



----------------
public static String[] execTokenizer(String cmd)
{
 int j,i,l,c,lc=' ',ct=0;
 String[] ret=null;
 int hazime=0,owari=0;

 if(cmd==null)return null;

 try{

 //count umber
 l=cmd.length();
 for(i=0;i<l;lc=c,i++){
  c=cmd.charAt(i);
  if(c=='\"'){
   for(j=i+1;j<l;lc=c,i++,j++){
    c=cmd.charAt(j);
    if(c=='\"'){
     ct++;
     break;
    }
   }
  }
  if(c==' ' && lc!=' '){
   ct++;
  }
 }
 if(lc!=' ' && lc!='\"'){
  ct++;
 }
 if(ct==0){
  return ret;
 }
 ret=new String[ct];
 ct=0;
 //
 l=cmd.length();
 for(i=0;i<l;lc=c,i++){
  c=cmd.charAt(i);

  if(c!=' ' && lc==' '){
   hazime=i;
  }

  if(c=='\"'){
   hazime=i+1;
   for(j=i+1;j<l;lc=c,i++,j++){
    c=cmd.charAt(j);
    if(c=='\"'){
     owari=j;
     ret[ct]=cmd.substring(hazime,owari);
     ct++;
     break;
    }
   }
  }
  if(c==' ' && lc!=' '){
   owari=i;
   ret[ct]=cmd.substring(hazime,owari);
   ct++;
  }
 }
 if(lc!=' ' && lc!='\"'){
  owari=i;
  ret[ct]=cmd.substring(hazime,owari);
  ct++;
 }

 }catch(Exception e){};

 return ret;
}




0 件のコメント:

コメントを投稿