У меня есть сценарий PHP, который должен вызывать сценарий оболочки, но совершенно не заботится о выводе. Сценарий оболочки выполняет несколько вызовов SOAP и выполняется медленно, поэтому я не хочу замедлять выполнение PHP-запроса, пока он ожидает ответа. Фактически, запрос PHP должен иметь возможность завершиться без завершения процесса оболочки.
Я изучил различные функции exec(), shell_exec(), pcntl_fork() и т. д., Но, похоже, ни одна из них не предлагает именно то, что мне нужно. (Или, если они это сделают, мне непонятно как.) Есть предложения?
Возможный дубликат php выполнить фоновый процесс






Если он «не заботится о выводе», нельзя ли вызвать exec для сценария с & для фонового процесса?
РЕДАКТИРОВАТЬ - включая то, что @ AdamTheHut прокомментировал к этому сообщению, вы можете добавить это к вызову exec:
" > /dev/null 2>/dev/null &"
Это перенаправит как stdio (первый >), так и stderr (2>) на /dev/null и будет работать в фоновом режиме.
Есть и другие способы сделать то же самое, но это самый простой для чтения.
Альтернатива указанному выше двойному перенаправлению:
" &> /dev/null &"
Кажется, это работает, но для этого нужно немного больше, чем амперсанд. Я заставил его работать, добавив "> / dev / null 2> / dev / null &" к вызову exec (). Хотя должен признать, я не совсем уверен, что это значит.
который перенаправляет stdio на / dev / null и stderr на / dev / null .. хороший улов при добавлении к вызову
Определенно лучший вариант, если вы хотите запустить и забыть с помощью php и apache. Во многих производственных средах Apache и PHP функция pcntl_fork () отключена.
Замечание для &> /dev/null &, xdebug не будет генерировать журналы, если вы его используете. Проверить stackoverflow.com/questions/4883171/…
Было бы более эффективно закрыть FD, а не открывать их повторно в / dev / null: <&- 1<&- 2<&-
как убить только что выполненный процесс?
@CharlesDuffy - Что именно делает <&- 1<&- 2<&-? Я погуглил и не нашел ответа.
@MichaelJMulligan закрывает файловые дескрипторы. Тем не менее, несмотря на повышение эффективности, в ретроспективе использование /dev/null является лучшей практикой, поскольку запись в закрытые FD вызывает ошибки, тогда как попытки чтения или записи в /dev/null просто молча ничего не дают.
@CharlesDuffy Это метод, которым я в конце концов воспользовался. Я попытался закрыть FD, но мой скрипт (в данном случае php-скрипт) просто переключился в фоновый режим в остановленном состоянии, пока я не выровнял его. Для этого потребовались аргументы командной строки, так что это могло быть частью этого
Фоновые сценарии будут убиты при перезапуске apache ... просто помните об этом для очень длинных заданий или если вам не повезло с точки зрения времени, и вы задаетесь вопросом, почему ваша работа исчезла ...
Просто хотел поделиться своим опытом: здесь 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 &');
Это работает, если скрипт php завершается нормально, но если я завершу процесс, например, нажав «ctrl + c» в командной строке, фоновый процесс завершится преждевременно. Есть идеи, как это предотвратить?
Есть ли способ сделать то же самое в Windows?
@fritzmg - не знаю: никогда не пробовал делать что-то подобное на машине с Windows
@fritzmg Посмотри на мой ответ; работает как под Windows, так и под Unix: stackoverflow.com/a/40243588/1412157
Этот ответ предоставляет более комплексное решение, если вам нужно проверить, выполняется ли все еще процесс: stackoverflow.com/a/45966/814160
@kapeels не идеальное решение, но вы можете использовать ~/log/mylog.log вместо /dev/null, чтобы записывать вывод
Я использовал для этого в, так как он действительно запускает независимый процесс.
<?php
`echo "the command"|at now`;
?>
в некоторых ситуациях это абсолютно лучшее решение. это был единственный способ, который помог мне выпустить "sudo reboot" ("echo 'sleep 3; sudo reboot' | at now") из веб-интерфейса И закончить рендеринг страницы .. на openbsd
если пользователь, у которого вы запускаете apache (обычно www-data), не имеет разрешений на использование at и вы не можете его настроить, вы можете попробовать использовать <?php exec('sudo sh -c "echo \"command\" | at now" ');. Если command содержит кавычки, см. побег, чтобы избавить вас от головной боли
Если подумать, заставить sudo запускать sh - не лучшая идея, так как это в основном дает sudo root-доступ ко всему. Я вернулся к использованию echo "sudo command" | at now и комментировал www-data в /etc/at.deny.
@Julien, возможно, вы могли бы создать сценарий оболочки с помощью команды sudo и вместо этого запустить его. пока вы не передаете значения, отправленные пользователем, в указанный скрипт
Пакет (и команда) at не по умолчанию для некоторых дистрибутивов Linux. Также необходимо, чтобы была запущена служба atd. Это решение может быть трудным для понимания из-за отсутствия объяснения.
В 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" $!';
Кроме того, как примечание от того, кто «был там раньше», любой, кто читает это, может подумать об использовании не только nice, но и ionice.
Что значит "% u" $! делать именно?
@Twigs & запускает предыдущий код в фоновом режиме, затем printf используется для форматированного вывода переменной $!, которая содержит PID
Большое вам спасибо, я пробовал всевозможные решения, которые находил для вывода в журнал с помощью асинхронного вызова оболочки PHP, и ваше единственное решение, которое удовлетворяет всем критериям.
У PHP-выполнить-фон-процесс есть несколько хороших предложений. Думаю, у меня неплохой, но я пристрастен :)
В Linux вы можете запустить процесс в новом независимом потоке, добавив амперсанд в конце команды
mycommand -someparam somevalue &
В Windows вы можете использовать команду DOS "запустить"
start mycommand -someparam somevalue
В Linux родительский элемент все еще может блокироваться, пока дочерний процесс не завершит работу, если он пытается читать из дескриптора открытого файла, удерживаемого подпроцессом (например, stdout), так что это не полное решение.
Проверена команда start в Windows, она не работает асинхронно ... Не могли бы вы указать источник, откуда вы получили эту информацию?
@ Alph.Dev, пожалуйста, взгляните на мой ответ, если вы используете Windows: stackoverflow.com/a/40243588/1412157
@mynameis Ваш ответ показывает, почему команда запуска НЕ работала. Это из-за параметра /B. Я объяснил это здесь: stackoverflow.com/a/34612967/1709903
Вы также можете запустить скрипт PHP как демон или cronjob: #!/usr/bin/php -q
правильный способ (!) сделать это -
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 уже пробовал это).
Используйте именованный 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.
Для всех пользователей 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 и т. д.
@davidvalentino Правильно, и ничего страшного! Если вы хотите выполнить скрипт PHP / Pyhton / NodeJS, вам нужно фактически вызвать исполняемый файл и передать ему свой скрипт. Например: вы не вставляете в свой терминал myscript.js, а вместо этого пишете node myscript.js. То есть: узел - это исполняемый файл, myscript.js - это сценарий для выполнения. Между исполняемым файлом и скриптом огромная разница.
правильно, в этом случае нет проблем, в других случаях, например, нужно запустить laravel artisan, php artisan просто оставьте здесь комментарий, поэтому не нужно отслеживать, почему он не будет работать с командами
без очереди использования вы можете использовать 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.
Предупреждение: если вы не дождетесь, процесс будет остановлен, когда запрос завершится
Идеальный инструмент, основанный на внутренних функциях proc_*.
Независимо от того, какое решение вы выберете, вам также следует рассмотреть возможность использования
niceиionice, чтобы сценарий оболочки не перегружал вашу систему (например,/usr/bin/ionice -c3 /usr/bin/nice -n19).