Асинхронная оболочка exec в PHP

У меня есть сценарий PHP, который должен вызывать сценарий оболочки, но совершенно не заботится о выводе. Сценарий оболочки выполняет несколько вызовов SOAP и выполняется медленно, поэтому я не хочу замедлять выполнение PHP-запроса, пока он ожидает ответа. Фактически, запрос PHP должен иметь возможность завершиться без завершения процесса оболочки.

Я изучил различные функции exec(), shell_exec(), pcntl_fork() и т. д., Но, похоже, ни одна из них не предлагает именно то, что мне нужно. (Или, если они это сделают, мне непонятно как.) Есть предложения?

Независимо от того, какое решение вы выберете, вам также следует рассмотреть возможность использования nice и ionice, чтобы сценарий оболочки не перегружал вашу систему (например, /usr/bin/ionice -c3 /usr/bin/nice -n19).

rinogo 02.08.2017 23:34

Возможный дубликат php выполнить фоновый процесс

Sean the Bean 01.02.2018 00:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
204
2
171 501
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Ответ принят как подходящий

Если он «не заботится о выводе», нельзя ли вызвать exec для сценария с & для фонового процесса?

РЕДАКТИРОВАТЬ - включая то, что @ AdamTheHut прокомментировал к этому сообщению, вы можете добавить это к вызову exec:

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

Это перенаправит как stdio (первый >), так и stderr (2>) на /dev/null и будет работать в фоновом режиме.

Есть и другие способы сделать то же самое, но это самый простой для чтения.


Альтернатива указанному выше двойному перенаправлению:

" &> /dev/null &"

Кажется, это работает, но для этого нужно немного больше, чем амперсанд. Я заставил его работать, добавив "> / dev / null 2> / dev / null &" к вызову exec (). Хотя должен признать, я не совсем уверен, что это значит.

AdamTheHutt 21.10.2008 23:04

который перенаправляет stdio на / dev / null и stderr на / dev / null .. хороший улов при добавлении к вызову

warren 22.10.2008 18:48

Определенно лучший вариант, если вы хотите запустить и забыть с помощью php и apache. Во многих производственных средах Apache и PHP функция pcntl_fork () отключена.

method 20.08.2009 01:10

Замечание для &> /dev/null &, xdebug не будет генерировать журналы, если вы его используете. Проверить stackoverflow.com/questions/4883171/…

kapeels 04.02.2011 07:43

Было бы более эффективно закрыть FD, а не открывать их повторно в / dev / null: <&- 1<&- 2<&-

Charles Duffy 16.05.2012 20:30

как убить только что выполненный процесс?

Jürgen Paul 23.07.2013 04:34

@CharlesDuffy - Что именно делает <&- 1<&- 2<&-? Я погуглил и не нашел ответа.

Mike 22.10.2013 18:01

@MichaelJMulligan закрывает файловые дескрипторы. Тем не менее, несмотря на повышение эффективности, в ретроспективе использование /dev/null является лучшей практикой, поскольку запись в закрытые FD вызывает ошибки, тогда как попытки чтения или записи в /dev/null просто молча ничего не дают.

Charles Duffy 22.10.2013 18:49

@CharlesDuffy Это метод, которым я в конце концов воспользовался. Я попытался закрыть FD, но мой скрипт (в данном случае php-скрипт) просто переключился в фоновый режим в остановленном состоянии, пока я не выровнял его. Для этого потребовались аргументы командной строки, так что это могло быть частью этого

Mike 23.10.2013 05:35

Фоновые сценарии будут убиты при перезапуске apache ... просто помните об этом для очень длинных заданий или если вам не повезло с точки зрения времени, и вы задаетесь вопросом, почему ваша работа исчезла ...

Julien 09.04.2015 18:15

Просто хотел поделиться своим опытом: здесь PHP не запускал процесс в фоновом режиме, пока я явно не использовал 2>/dev/null >/dev/null. Например, попытка перенаправить вывод в файл приводила к зависанию apache до завершения выполнения. Итак, мой подход заключался в том, чтобы «направить» вывод на tee перед перенаправлением на /dev/null. Как это: shell_exec('my_script.sh 2>&1 | tee -a mylog.log 2>/dev/null >/dev/null &');

Alexandre Schmidt 16.12.2015 05:32

Это работает, если скрипт php завершается нормально, но если я завершу процесс, например, нажав «ctrl + c» в командной строке, фоновый процесс завершится преждевременно. Есть идеи, как это предотвратить?

tslater 12.01.2016 19:32

Есть ли способ сделать то же самое в Windows?

fritzmg 10.02.2016 13:53

@fritzmg - не знаю: никогда не пробовал делать что-то подобное на машине с Windows

warren 10.02.2016 17:32

@fritzmg Посмотри на мой ответ; работает как под Windows, так и под Unix: stackoverflow.com/a/40243588/1412157

LucaM 25.10.2016 18:13

Этот ответ предоставляет более комплексное решение, если вам нужно проверить, выполняется ли все еще процесс: stackoverflow.com/a/45966/814160

Sean the Bean 01.02.2018 00:49

@kapeels не идеальное решение, но вы можете использовать ~/log/mylog.log вместо /dev/null, чтобы записывать вывод

Garet Claborn 03.04.2018 04:32

Я использовал для этого в, так как он действительно запускает независимый процесс.

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

в некоторых ситуациях это абсолютно лучшее решение. это был единственный способ, который помог мне выпустить "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") из веб-интерфейса И закончить рендеринг страницы .. на openbsd

Kaii 13.08.2009 22:47

если пользователь, у которого вы запускаете apache (обычно www-data), не имеет разрешений на использование at и вы не можете его настроить, вы можете попробовать использовать <?php exec('sudo sh -c "echo \"command\" | at now" ');. Если command содержит кавычки, см. побег, чтобы избавить вас от головной боли

Julien 09.04.2015 17:21

Если подумать, заставить sudo запускать sh - не лучшая идея, так как это в основном дает sudo root-доступ ко всему. Я вернулся к использованию echo "sudo command" | at now и комментировал www-data в /etc/at.deny.

Julien 16.04.2015 10:47

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

Garet Claborn 03.04.2018 04:35

Пакет (и команда) at не по умолчанию для некоторых дистрибутивов Linux. Также необходимо, чтобы была запущена служба atd. Это решение может быть трудным для понимания из-за отсутствия объяснения.

rzlvmp 09.11.2020 10:18

В Linux вы можете делать следующее:

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

Это выполнит команду в командной строке, а затем просто вернет 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 02.08.2017 23:31

Кроме того, как примечание от того, кто «был там раньше», любой, кто читает это, может подумать об использовании не только nice, но и ionice.

rinogo 02.08.2017 23:32

Что значит "% u" $! делать именно?

MortiestMorty 25.10.2017 04:56

@Twigs & запускает предыдущий код в фоновом режиме, затем printf используется для форматированного вывода переменной $!, которая содержит PID

NoChecksum 13.01.2019 20:01

Большое вам спасибо, я пробовал всевозможные решения, которые находил для вывода в журнал с помощью асинхронного вызова оболочки PHP, и ваше единственное решение, которое удовлетворяет всем критериям.

Josh Powlison 30.12.2019 02:17

У PHP-выполнить-фон-процесс есть несколько хороших предложений. Думаю, у меня неплохой, но я пристрастен :)

В Linux вы можете запустить процесс в новом независимом потоке, добавив амперсанд в конце команды

mycommand -someparam somevalue &

В Windows вы можете использовать команду DOS "запустить"

start mycommand -someparam somevalue

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

Charles Duffy 16.05.2012 20:31

Проверена команда start в Windows, она не работает асинхронно ... Не могли бы вы указать источник, откуда вы получили эту информацию?

Slava 04.01.2016 15:44

@ Alph.Dev, пожалуйста, взгляните на мой ответ, если вы используете Windows: stackoverflow.com/a/40243588/1412157

LucaM 25.10.2016 18:12

@mynameis Ваш ответ показывает, почему команда запуска НЕ ​​работала. Это из-за параметра /B. Я объяснил это здесь: stackoverflow.com/a/34612967/1709903

Slava 28.10.2016 14:28

Вы также можете запустить скрипт PHP как демон или cronjob: #!/usr/bin/php -q

правильный способ (!) сделать это -

  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();

Проблема с pcntl_fork () заключается в том, что вы не должны использовать его при работе под веб-сервером, как это делает OP (кроме того, OP уже пробовал это).

Guss 11.05.2010 19:13

Используйте именованный fifo.

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

Затем, когда вы захотите запустить длительную задачу, просто напишите новую строку (неблокирующую для файла триггера.

Пока ваш ввод меньше, чем PIPE_BUF, и это одна операция write(), вы можете записывать аргументы в fifo и отображать их как $REPLY в скрипте.

Я использовал это ...

/** 
 * 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 - это константа, определенная как define('PHP_PATH', '/opt/bin/php5') или аналогичный)

Он передает аргументы через командную строку. Чтобы прочитать их на PHP, см. argv.

Единственный способ, который действительно работал у меня, был:

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

'disown' встроен в Bash и не работает с shell_exec () таким образом. Я попробовал shell_exec("/usr/local/sbin/command.sh 2>&1 >/dev/null | at now & disown") и получил только: sh: 1: disown: not found.

Thomas Daugaard 15.01.2013 16:52

Для всех пользователей 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 16.01.2020 15:32

@davidvalentino Правильно, и ничего страшного! Если вы хотите выполнить скрипт PHP / Pyhton / NodeJS, вам нужно фактически вызвать исполняемый файл и передать ему свой скрипт. Например: вы не вставляете в свой терминал myscript.js, а вместо этого пишете node myscript.js. То есть: узел - это исполняемый файл, myscript.js - это сценарий для выполнения. Между исполняемым файлом и скриптом огромная разница.

LucaM 16.01.2020 17:59

правильно, в этом случае нет проблем, в других случаях, например, нужно запустить laravel artisan, php artisan просто оставьте здесь комментарий, поэтому не нужно отслеживать, почему он не будет работать с командами

david valentino 17.01.2020 10:43

без очереди использования вы можете использовать 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);

Я также нашел для этого полезным Компонент процесса 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.

Предупреждение: если вы не дождетесь, процесс будет остановлен, когда запрос завершится

the_nuts 09.01.2019 16:41

Идеальный инструмент, основанный на внутренних функциях proc_*.

MAChitgarha 28.01.2019 23:07

Другие вопросы по теме