Как стать автором
Обновить

«Правильный» system() для PHP-CLI

Время на прочтение 2 мин
Количество просмотров 13K
Внимание! Все рассуждения относятся к запуску PHP в режиме CLI, а не как модуль веб-сервера!

Если вы когда-либо использовали функцию system() в PHP, вы наверняка задавались вопросом: каким образом system() возвращает последнюю строку для команды, да ещё и выводит результаты исполнения команды на веб-страницу, а не себе куда-то в stdout веб-сервера? И почему system() работает не так, как system() в Си? Ответ, в общем-то, проще, чем может показаться.

На самом деле, system() в PHP использует ту же самую функцию, что и exec() (int php_exec(int type, char *cmd, pval *array, pval *return_value TSRMLS_DC)), просто он выводит результат построчно, не накапливая его в массив, как exec(), а используя лишь строку в качестве буфера. На это поведение, кстати, намекает документация к функции system():
The system() call also tries to automatically flush the web server's output buffer after each line of output if PHP is running as a server module.

А php_exec в своей основе использует popen() для исполнения соответствующей команды, а значит, есть вероятность, что вызываемая программа может «не признать» STDIN за терминал и выдать ответ в другом формате, нежели при запуске через shell.

Решение проблемы построчной буферизации system()


Если вы просто хотите сделать так, чтобы у вас выводилась информация по мере поступления, не накапливаясь построчно, вы можете просто использовать passthru вместо system. Этого достаточно для того, чтобы, скажем, git clone выводил прогресс выполнения операции корректно, но, при этом, ls будет всё равно выводить информацию в один столбец, поскольку, на самом деле, passthru тоже использует php_exec, а значит и popen, со всеми вытекающими последствиями

Вариант решения с proc_open()


Если вы хотите, чтобы, скажем, ls выводил-таки информацию также, как он это делает, будучи запущенным из терминала, а также если вам нужна поддержка цветов, можно использовать proc_open:
$handle = proc_open("ls", array(0=>STDIN,1=>STDOUT,2=>STDERR), $pipes);
$retval = proc_close($handle);


UNIX-way


В принципе, приведенных выше способов должно быть достаточно, чтобы у вас всё работало.
Но если вам нужны какие-то особые извращения, или вы хотите полностью контролировать, что у вас происходит при запуске программы, вы можете попробовать решить эту проблему старым UNIX-way: через fork-exec (работать это будет только под *nix и при условии наличия расширения pcntl):
function libcSystem($cmd) {
    $pid = pcntl_fork();
    if($pid < 0) {
        return -1;
    }
    if($pid == 0) {
        pcntl_exec('/bin/sh', array('-c', $cmd), $_ENV);
        exit(127);
    }

    pcntl_waitpid($pid, $status);
    return pcntl_wexitstatus($status);
}


Надеюсь, эта статья будет полезна тем, кто пишет скрипты на PHP для CLI и хочет понять, как заставить system() работать так, как он должен бы по идее работать.
Теги:
Хабы:
+57
Комментарии 6
Комментарии Комментарии 6

Публикации

Истории

Работа

PHP программист
175 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн