Правильное выполнение оболочки в PHP

Проблема

Я использовал функцию, которая использовала proc_open() для вызова команд оболочки. Кажется, что я делал STDIO неправильно, а иногда вызывал PHP или целевую команду для блокировки. Это исходный код:

 function execute($cmd, $stdin=null){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); fwrite($pipes[0],$stdin); fclose($pipes[0]); $stdout=stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr=stream_get_contents($pipes[2]); fclose($pipes[2]); return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) ); } устройство function execute($cmd, $stdin=null){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); fwrite($pipes[0],$stdin); fclose($pipes[0]); $stdout=stream_get_contents($pipes[1]); fclose($pipes[1]); $stderr=stream_get_contents($pipes[2]); fclose($pipes[2]); return array( 'stdout'=>$stdout, 'stderr'=>$stderr, 'return'=>proc_close($proc) ); } 

Это работает большую часть времени , но этого недостаточно, я хочу заставить его работать всегда.

Проблема заключается в stream_get_contents() , если буферы STDIO превышают 4 тыс. Данных.

Прецедент

 function out($data){ file_put_contents('php://stdout',$data); } function err($data){ file_put_contents('php://stderr',$data); } if(isset($argc)){ // RUN CLI TESTCASE out(str_repeat('o',1030); err(str_repeat('e',1030); out(str_repeat('O',1030); err(str_repeat('E',1030); die(128); // to test return error code }else{ // RUN EXECUTION TEST CASE $res=execute('php -f '.escapeshellarg(__FILE__)); } 

Мы выводим строку дважды в STDERR и STDOUT с общей длиной 4120 байт (более 4k). Это заставляет PHP блокироваться с обеих сторон.

Решение

По-видимому, stream_select() – это путь. У меня есть следующий код:

 function execute($cmd,$stdin=null,$timeout=20000){ $proc=proc_open($cmd,array(0=>array('pipe','r'),1=>array('pipe','w'),2=>array('pipe','w')),$pipes); $write = array($pipes[0]); $read = array($pipes[1], $pipes[2]); $except = null; $stdout = ''; $stderr = ''; while($r = stream_select($read, $write, $except, null, $timeout)){ foreach($read as $stream){ // handle STDOUT if($stream===$pipes[1]) /*...*/ $stdout.=stream_get_contents($stream); // handle STDERR if($stream===$pipes[2]) /*...*/ $stderr.=stream_get_contents($stream); } // Handle STDIN (???) if(isset($write[0])) ; // the following code is temporary $n=isset($n) ? $n+1 : 0; if($n>10)break; // break while loop after 10 iterations } } 

Единственный оставшийся фрагмент головоломки – обработка STDIN (см. Строку, помеченную (???) ). Я понял, что STDIN должен быть обеспечен тем, что вызывает мою функцию, execute() . Но что, если я вообще не хочу использовать STDIN? В моем тестовом тесте, выше, я не просил ввода, но я должен что-то сделать для STDIN.

Тем не менее, вышеупомянутый подход все еще зависает в stream_get_contents() . Я совершенно не уверен, что делать / попробовать дальше.

кредиты

Решение было предложено Якобом Труэльсеном, а также обнаружение оригинальной проблемы. Идея 4k была его идеей. До этого я был озадачен тем, почему функция работает нормально (не знал, что все зависит от размера буфера).

Ну, кажется, год прошел и забыл, что эта вещь все еще ждет!

Тем не менее, я завернул этот беспорядок в хороший PHP-класс, который вы можете найти в Github .

Основная проблема заключается в том, что чтение STDERR заставляет скрипт PHP блокироваться, поэтому он отключен.

С яркой стороны, благодаря событиям и некоторому хорошему кодированию (надеюсь!), Можно фактически взаимодействовать с выполняемым процессом (отсюда и имя класса, InterExec ). Таким образом, вы можете иметь поведение стиля бота в PHP.

Вы пропустили это примечание в руководстве PHP для stream_select ():

Когда stream_select () возвращается, массивы read, write и except изменяются, чтобы указать, какие потоковые ресурсы фактически изменили статус.

Вам необходимо повторно создать массивы, прежде чем вызывать stream_select () каждый раз.

В зависимости от процесса, который вы открываете, это может быть причиной того, что ваш пример все еще блокируется.

 while($r = stream_select($read, $write, $except, null, $timeout)){ 

Насколько я знаю, это установит $ r в количество измененных потоков, которое может быть 0, и цикл больше не будет продолжаться. Я бы лично перекодировал это, как описано в руководстве PHP:

 while(false !== ($r = stream_select($read, $write, $except, null, $timeout))){ 

Что касается вашего STDIN, если ваш процесс не является интерактивным, STDIN может не понадобиться. Каков процесс, который вы выполняете?

Вся проблема с зависанием в stream_get_contents заключается в том, как создается процесс. Правильный способ – открыть STDOUT с режимом чтения / записи на трубе, например:

 $descriptor = array (0 => array ("pipe", "r"), 1 => array ("pipe", "rw"), 2 => array ("pipe", "rw")); //Open the resource to execute $command $t->pref = proc_open($command,$descriptor,$t->pipes); //Set STDOUT and STDERR to non-blocking stream_set_blocking ($t->pipes[0], 0); stream_set_blocking ($t->pipes[1], 0); 

Очевидно, что когда stream_get_contents хочет прочитать трубу STDOUT, ему нужен режим чтения. Та же ошибка с hang / freeze / block находится в этом хорошем классе https://gist.github.com/Arbow/982320

Затем блокировка исчезает. Но чтение ничего не читает.