PHPの非同期シェルexec


199

シェルスクリプトを呼び出す必要があるが、出力はまったく気にしないPHPスクリプトを持っています。シェルスクリプトは多数のSOAP呼び出しを実行し、完了するのに時間がかかるため、応答を待機している間、PHPリクエストの速度を低下させたくありません。実際、PHPリクエストは、シェルプロセスを終了せずに終了できるはずです。

私は様々に見てきましたexec()shell_exec()pcntl_fork()、などの機能、それらのどれもまさに私が望むものを提供するように見えるん。(または、もしそうなら、それは私には明確ではありません。)何か提案はありますか?


どちらのソリューションを選択する場合でも、シェルスクリプトの使用niceを検討しionice、シェルスクリプトがシステムに/usr/bin/ionice -c3 /usr/bin/nice -n19
負荷をかけ

回答:


218

「出力を気にしない」場合、スクリプトのexecをで呼び出して&プロセスをバックグラウンド化できませんでしたか?

編集 -@ AdamTheHutがこの投稿にコメントした内容を組み込んで、これを呼び出しに追加できますexec

" > /dev/null 2>/dev/null &"

つまり、両方をリダイレクトするstdio(第一>)及びstderr2>)の/dev/nullしてバックグラウンドで実行します。

同じことを行う方法は他にもありますが、これが最も簡単に読むことができます。


上記の二重リダイレクトの代替:

" &> /dev/null &"

11
これは機能しているようですが、アンパサンドだけでは不十分です。exec /呼び出しに "> / dev / null 2> / dev / null&"を追加することで機能しました。私は認めざるを得ませんが、それが何をするのか正確にはわかりません。
AdamTheHutt 08年

2
間違いなくphpとapacheで火事を忘れて忘れたくなったら、どうぞ。多くの製品のApacheおよびPHP環境では、pcntl_fork()が無効になります。
メソッド

9
の注意点として&> /dev/null &、これを使用してもxdebugはログを生成しません。stackoverflow.com/questions/4883171/…を
kapeels

5
@MichaelJMulliganはファイル記述子を閉じます。とはいえ、効率的な向上にもかかわらず、後/dev/nullから考えると、閉じたFDへの書き込みはエラーを引き起こすのに対し、/dev/null単に黙って何もしないで読み書きしようとすると、使用することをお勧めします。
Charles Duffy

3
Apacheの再起動時にバックグラウンドスクリプトが強制終了されます...非常に長いジョブの場合、またはタイミング的に不運でジョブが消えた理由に疑問がある場合に注意してください...
Julien

53

独立したプロセスを実際に開始しているので、私はこれに使用ました。

<?php
    `echo "the command"|at now`;
?>

4
状況によっては、これが絶対に最良の解決策です。OpenBSDの上の...ページをレンダリングするのWebGUIとフィニッシュから|;(「今でエコー『sudoをリブート睡眠3』」)には、「sudoのリブート」をリリースする私のために働いただけだった
魁夷

1
Apache(通常はwww-data)を実行しているユーザーに使用する権限atがなく、設定できない<?php exec('sudo sh -c "echo \"command\" | at now" ');場合commandは、使用を試みることができます。引用符が含まれている場合は、escapeshellargを参照して頭痛を軽減してください
Julien

1
よく考えて、sudoにshを実行させることは、sudoに基本的にすべてへのrootアクセスを与えるため、最良のアイデアではありません。私は使用に戻りecho "sudo command" | at now、コメントアウトwww-dataしました/etc/at.deny
Julien

@Julien多分あなたはsudoコマンドでシェルスクリプトを作成し、代わりにそれを起動することができます。ユーザーが送信した値をスクリプトに渡していない限り
Garet Claborn

26

すべてのWindowsユーザーに:非同期PHPスクリプトを実行するための良い方法を見つけました(実際には、ほとんどすべてのスクリプトで動作します)。

popen()およびpclose()コマンドに基づいています。また、WindowsとUnixの両方でうまく機能します。

function execInBackground($cmd) {
    if (substr(php_uname(), 0, 7) == "Windows"){
        pclose(popen("start /B ". $cmd, "r")); 
    }
    else {
        exec($cmd . " > /dev/null &");  
    }
} 

元のコード:http : //php.net/manual/en/function.exec.php#86329


これはファイルを実行するだけで、php / python / nodeなどを使用している場合は機能しません
david valentino

@davidvalentino正解、それで結構です!PHP / Pyhton / NodeJSスクリプトを実行する場合は、実際に実行可能ファイルを呼び出してスクリプトに渡す必要があります。例:あなたはあなたのターミナルに入れませんmyscript.jsが、代わりにあなたは書くでしょうnode myscript.js。つまり、nodeは実行可能ファイル、myscript.jsは実行するスクリプトです。実行可能ファイルとスクリプトには大きな違いがあります。
LucaM

その場合は問題ありません。他の例では、laravel artisanを実行する必要がある場合など、php artisanここにコメントを入力して、コマンドで機能しない理由を追跡する必要はありません
david valentino

22

Linuxでは、次のことができます。

$cmd = 'nohup nice -n 10 php -f php/file.php > log/file.log & printf "%u" $!';
$pid = shell_exec($cmd);

これにより、コマンドプロンプトでコマンドが実行され、PIDが返されます。PIDが> 0かどうかを確認して、正常に機能したことを確認できます。

この質問も同様です。PHPにはスレッドがありますか?


この答えは、必要最低限​​の要素のみを含めた方が読みやすくなります(action=generate var1_id=23 var2_id=35 gen_id=535セグメントを削除)。また、OPはシェルスクリプトの実行について尋ねたため、PHP固有の部分は必要ありません。最終的なコードは次のようになります$cmd = 'nohup nice -n 10 /path/to/script.sh > /path/to/log/file.log & printf "%u" $!';
rinogo

また、「以前に行ったことがある」という人からのメモとして、これを読んでいる人は、だけでniceなくも使用することを検討するかもしれませんionice
rinogo 2017

1
"%u"とは何ですか$!正確に?
2017年

@Twigs &は前のコードをバックグラウンドで実行し、PIDを含む変数のprintfフォーマットされた出力に使用され$!ます
NoChecksum

1
どうもありがとうございました。非同期のPHPシェル呼び出しでログに出力するために見つけたあらゆる種類の解決策を試しましたが、すべての基準を満たしているのはあなただけです。
Josh Powlison


6

Linuxでは、コマンドの最後にアンパサンドを追加することにより、新しい独立したスレッドでプロセスを開始できます。

mycommand -someparam somevalue &

Windowsでは、「開始」DOSコマンドを使用できます。

start mycommand -someparam somevalue

2
Linuxでは、子プロセスが保持しているオープンファイルハンドル(つまり、stdout)から読み取ろうとすると、子プロセスの実行が終了するまで親プロセスがブロックする可能性があるため、これは完全なソリューションではありません。
Charles Duffy

1
startWindowsでコマンドをテストしましたが、非同期に実行されません...その情報を取得したソースを含めることができますか?
Alph.Dev

@ Alph.Dev Windowsを使用している場合は、私の答えを確認してください:stackoverflow.com/a/40243588/1412157
LucaM

@mynameis答えは、開始コマンドが機能しなかった理由を正確に示しています。その/Bパラメータのためです。ここで説明しました:stackoverflow.com/a/34612967/1709903
Alph.Dev

6

それを行う正しい方法(!)は

  1. フォーク()
  2. setsid()
  3. execve()

fork forks、setsidは現在のプロセスをマスタープロセス(親なし)にするように指示し、execveは呼び出しプロセスを呼び出されたプロセスに置き換えるように指示します。親が子に影響を与えずに終了できるように。

 $pid=pcntl_fork();
 if($pid==0)
 {
   posix_setsid();
   pcntl_exec($cmd,$args,$_ENV);
   // child becomes the standalone detached process
 }

 // parent's stuff
 exit();

6
pcntl_fork()の問題は、OPが行うように、Webサーバーの下で実行するときにそれを使用することが想定されていないことです(OPはすでにこれを試しました)。
Guss

6

私はこれを使いました...

/** 
 * Asynchronously execute/include a PHP file. Does not record the output of the file anywhere.  
 * Relies on the PHP_PATH config constant.
 *
 * @param string $filename  file to execute
 * @param string $options   (optional) arguments to pass to file via the command line
 */ 
function asyncInclude($filename, $options = '') {
    exec(PHP_PATH . " -f {$filename} {$options} >> /dev/null &");
}

(どこ PHP_PATH constがdefine('PHP_PATH', '/opt/bin/php5')類似または類似して定義されているか)

コマンドラインから引数を渡します。それらをPHPで読むには、argvを参照してください。


4

私が本当にうまくいったことがわかった唯一の方法は、

shell_exec('./myscript.php | at now & disown')

3
'disown'はBashビルトインであり、この方法ではshell_exec()で動作しません。私が試したshell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown")と私が得るすべてがある:sh: 1: disown: not found
トーマスDaugaard

4

Symfonyプロセスコンポーネントもこれに役立ちます。

use Symfony\Component\Process\Process;

$process = new Process('ls -lsa');
// ... run process in background
$process->start();

// ... do other things

// ... if you need to wait
$process->wait();

// ... do things after the process has finished

GitHubリポジトリでどのように機能するかをご覧ください。


2
警告:待たなければ、リクエストが終了するとプロセスが
強制

proc_*内部機能に基づいた完璧なツール。
MAChitgarha


1

名前付きfifoを使用します。

#!/bin/sh
mkfifo trigger
while true; do
    read < trigger
    long_running_task
done

次に、長時間実行されるタスクを開始したいときはいつでも、改行(トリガーファイルに非ブロック化)を書き込むだけです。

入力がそれよりも小さくPIPE_BUF、単一のwrite()操作である限り、引数をfifoに書き込ん$REPLYで、スクリプトのように表示させることができます。


1

キューを使用せずに、次のproc_open()ように使用できます:

    $descriptorspec = array(
        0 => array("pipe", "r"),
        1 => array("pipe", "w"),
        2 => array("pipe", "w")    //here curaengine log all the info into stderror
    );
    $command = 'ping stackoverflow.com';
    $process = proc_open($command, $descriptorspec, $pipes);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.