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

    Внимание! Все рассуждения относятся к запуску 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
    • 10,8k
    • 6
    Поделиться публикацией

    Комментарии 6

      +8
      Интересно узнать мнение тех, кто голосует против моего топика: что именно вам не нравится :)? Что я говорю про PHP? Или есть какие-то объективные недостатки моей статьи?
        +1
        Может поэтому — ru.php.net/manual/en/function.system.php?
          +5
          А где там написано про решение проблемы с построчной буферизацией у system()?
            +3
            да, если там нет данного примера в коментариях, то вам следует его там разместить с коментированием на английском языке.
        +6
        Не обращайте внимания на минусы. Это специфика данного ресурса.
          –2
          А exec работает не так? Очень редко приходится пользоваться этими функциями, я даже их запрещаю везде, поэтому и интересуюсь.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое