Команды операционной системы в PHP: выполнение и управление
Основы выполнения команд ОС в PHP
PHP предоставляет несколько встроенных функций для запуска внешних программ и команд операционной системы. Выбор метода зависит от потребностей: нужно ли получить вывод, управлять вводом/выводом, работать в фоне или обрабатывать ошибки. Ниже рассмотрены основные подходы.
Наиболее эффективное решение: функция exec с контролем кода возврата
Функция exec выполняет команду и возвращает последнюю строку вывода. Код завершения команды записывается в третий параметр. Это позволяет проверить успешность выполнения и избежать типичных проблем.
$output = [];
$returnCode = 0;
$lastLine = exec('ls -l', $output, $returnCode);
if ($returnCode === 0) {
echo 'Команда выполнена успешно';
print_r($output);
} else {
echo 'Ошибка выполнения, код: ' . $returnCode;
}
Php выполнение команд (выполнение команд ос из php)
Пояснение:
execпринимает команду, массив для вывода (каждая строка - элемент) и ссылку на код возврата.- Код 0 обычно означает успех, ненулевой - ошибку.
- Вывод помещается в массив, удобно для дальнейшей обработки.
Типичные проблемы:
- Пустой массив
$outputпри ошибке - проверяйте$returnCode. - Игнорирование экранирования - используйте
escapeshellarg()для аргументов. - Длительное выполнение без таймаута - применяйте
set_time_limit(0)илиproc_openс управлением.
Как получить полный вывод команды без разбиения на строки?
Для этого используется shell_exec (или обратные кавычки). Возвращает всю строку вывода, но не даёт кода возврата.
$output = shell_exec('whoami');
echo 'Текущий пользователь: ' . $output;
Php выполнение кода (выполнение php-кода)
Проблемы:
- При ошибке команды возвращается
null- сложно отличить от пустого вывода. - Небезопасно при подстановке пользовательских данных - требуется экранирование.
Как выполнить команду и сразу вывести результат в поток?
Функции system и passthru выводят данные напрямую в stdout. passthru подходит для бинарных данных.
echo 'Содержимое папки:';
system('ls -la');
// Для бинарных данных, например, изображения
header('Content-Type: image/png');
passthru('cat image.png');
Ошибки:
- Нельзя перехватить вывод в переменную без буферизации.
systemвозвращает код возврата, но только последнюю строку, что неудобно.
Как выполнить команду с полным контролем над вводом/выводом и дескрипторами?
proc_open предоставляет максимальную гибкость: можно передать данные в stdin, читать из stdout и stderr, управлять процессом.
$descriptorspec = [
0 => ['pipe', 'r'], // stdin
1 => ['pipe', 'w'], // stdout
2 => ['pipe', 'w'], // stderr
];
$process = proc_open('grep error', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], 'error found\nno error here\nerror again\n');
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$returnCode = proc_close($process);
echo "Код: $returnCode\nStdout:\n$stdout\nStderr:\n$stderr";
}
Типичные сложности:
- Блокировки при чтении/записи - используйте неблокирующий ввод/вывод или потоковую обработку.
- Утечка ресурсов - обязательно закрывайте все pipes и вызывайте
proc_close. - Синтаксис дескрипторов может различаться в Windows.
Как выполнить фоновую команду без ожидания результата?
В Unix можно перенаправить вывод в /dev/null и добавить & в команду. В PHP часто используют exec с перенаправлением или popen с режимом r.
exec('nohup php long_script.php > /dev/null 2>&1 &');
Проблемы:
- Процесс может быть убит при завершении родительского скрипта (используйте
nohup). - Нельзя легко отследить завершение, если не сохранить PID.
Расширенные примеры выполнения команд
Пример 1: Запуск скрипта с передачей аргументов и безопасным экранированием
$filename = 'файл с пробелами.txt';
$escaped = escapeshellarg($filename);
$cmd = "wc -l $escaped";
$lines = shell_exec($cmd);
echo "Количество строк: " . trim($lines);
Количество строк: 42
Пояснение:
Функция escapeshellarg оборачивает строку в кавычки и экранирует спецсимволы, предотвращая инъекции.
Пример 2: Чтение stderr вместе с stdout через proc_open
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open('ls nonexistent', $descriptorspec, $pipes);
if (is_resource($process)) {
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$ret = proc_close($process);
echo "STDOUT: $stdout\nSTDERR: $stderr\nКод возврата: $ret";
}
STDOUT: STDERR: ls: nonexistent: No such file or directory Код возврата: 1
Пример 3: Выполнение команды с таймаутом через proc_open и proc_terminate
$cmd = 'sleep 10';
$process = proc_open($cmd, [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes);
$start = time();
$timeout = 3; // секунды
$status = proc_get_status($process);
while ($status['running'] && (time() - $start) < $timeout) {
usleep(100000); // 0.1 сек
$status = proc_get_status($process);
}
if ($status['running']) {
proc_terminate($process, 9); // SIGKILL
echo "Процесс прерван по таймауту\n";
}
proc_close($process);
Процесс прерван по таймауту
Пример 4: Использование popen для потоковой обработки вывода (построчное чтение)
$handle = popen('tail -f /var/log/syslog 2>&1', 'r');
if ($handle) {
while (!feof($handle)) {
$line = fgets($handle);
if ($line !== false) {
echo $line;
ob_flush(); flush();
}
}
pclose($handle);
}
Пример 5: Множественные команды в конвейере через proc_open (объединение pipes)
$cmd1 = 'cat /etc/passwd';
$cmd2 = 'grep -v "^#"';
$cmd3 = 'cut -d: -f1';
$p1 = proc_open($cmd1, [1 => ['pipe', 'w']], $pipes1);
if (is_resource($p1)) {
$p2 = proc_open($cmd2, [0 => $pipes1[1], 1 => ['pipe', 'w']], $pipes2);
if (is_resource($p2)) {
$p3 = proc_open($cmd3, [0 => $pipes2[1], 1 => ['pipe', 'w']], $pipes3);
if (is_resource($p3)) {
fclose($pipes1[1]);
fclose($pipes2[1]);
$result = stream_get_contents($pipes3[1]);
fclose($pipes3[1]);
proc_close($p3);
proc_close($p2);
proc_close($p1);
echo $result;
}
}
}
root daemon bin ...
Пример 6: Запуск фонового процесса с сохранением PID
// Сохраняем PID в файл для последующего контроля
$cmd = 'nohup php worker.php > worker.log 2>&1 & echo $!';
$pid = trim(shell_exec($cmd));
file_put_contents('worker.pid', $pid);
$running = (bool)shell_exec("kill -0 $pid 2>/dev/null && echo 1 || echo 0");
echo ($running ? 'Работает' : 'Не найден');
Пример 7: Взаимодействие с интерактивной командой (ввод/вывод через proc_open)
$descriptorspec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w']
];
$process = proc_open('python3 -i', $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], "print('Hello from PHP')\n");
fwrite($pipes[0], "exit()\n");
fclose($pipes[0]);
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
echo $output;
}
Hello from PHP