Создание консольных сценариев PHP на операционной системе Linux

Раздел: Программирование на PHP -> CLI приложения

Основы написания PHP скриптов для командной строки в Linux

Наиболее эффективным способом создания исполняемого PHP скрипта под Linux является использование shebang и установка прав на выполнение. Такой подход позволяет запускать скрипт без явного указания интерпретатора.

#!/usr/bin/env php
<?php
echo "Hello, Linux!\n";

скрипт php linux (написание скрипта на php для linux)

После сохранения файла (например, ~/hello.php) необходимо сделать его исполняемым:

chmod +x ~/hello.php

Теперь запуск осуществляется командой ./hello.php. При этом вывод будет направлен в терминал.

Распространенная ошибка: если первый символ shebang указан неверно (например, лишний пробел) или файл не имеет прав на выполнение, появится сообщение "command not found". Решение: проверить shebang (должен быть #!/usr/bin/env php) и права chmod +x.

Проблема кодировки: если скрипт содержит кириллицу, терминал может отображать кракозябры. Решение: установить локаль (export LANG=ru_RU.UTF-8) или использовать в коде iconv/mb_convert_encoding.

Как передать аргументы командной строки в PHP скрипт?

Для работы с аргументами доступны глобальные массивы $argv и $argc. Первый элемент $argv[0] содержит имя скрипта. Пример:

#!/usr/bin/env php
<?php
echo "Количество аргументов: $argc\n";
foreach ($argv as $i => $arg) {
    echo "arg[$i] = $arg\n";
}

Запуск ./script.php foo bar выведет:

Количество аргументов: 3
arg[0] = ./script.php
arg[1] = foo
arg[2] = bar

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

Как читать ввод с клавиатуры в консольном PHP?

Для чтения из STDIN используется функция fgets(STDIN). Пример интерактивного запроса имени:

#!/usr/bin/env php
<?php
echo "Введите ваше имя: ";
$name = trim(fgets(STDIN));
echo "Привет, $name!\n";

Также можно читать многострочный ввод, используя цикл до EOF (Ctrl+D).

Проблема: если скрипт запускается из конвейера (pipe), fgets(STDIN) прочитает переданные данные, что может быть неочевидно. Для интерактивного режима следует проверять, что STDIN является терминалом.

Как обрабатывать длинные опции (--option) и короткие (-o) в PHP?

Для разбора опций используется функция getopt(). Пример:

#!/usr/bin/env php
<?php
$options = getopt("f:v", ["file:", "verbose"]);
if (isset($options['file']) || isset($options['f'])) {
    $file = $options['file'] ?? $options['f'];
    echo "Обрабатываем файл: $file\n";
}
if (isset($options['v']) || isset($options['verbose'])) {
    echo "Режим подробного вывода включен\n";
}

Запуск ./script.php --file=log.txt -v даст соответствующий вывод.

Типичная ошибка: если опция требует значение, но оно не указано, getopt вернет пустую строку или false. Следует проверять isset перед использованием.

Как создать фоновый процесс (демон) на PHP?

Для перевода скрипта в фоновый режим используется комбинация posix_setsid() и отключение от терминала. Пример простого демона:

#!/usr/bin/env php
<?php
$pid = pcntl_fork();
if ($pid == -1) {
    die("Ошибка форка");
} elseif ($pid) {
    exit(0); // Родитель завершается
}
posix_setsid(); // Создает новую сессию
// Перенаправление STDIN/STDOUT/STDERR в /dev/null
fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
$stdin  = fopen('/dev/null', 'r');
$stdout = fopen('/var/log/mydaemon.log', 'ab');
$stderr = fopen('/var/log/mydaemon_error.log', 'ab');
// Основной цикл
while (true) {
    // ... работа ...
    sleep(10);
}

Такой скрипт после запуска отсоединится от терминала и будет работать в фоне.

Проблема: демон может "повиснуть" при ошибке. Необходимо обрабатывать сигналы (SIGTERM, SIGINT) для корректного завершения. Также нужно отслеживать родительские процессы и избегать зомби.

Как использовать библиотеку Symfony Console для создания CLI команд?

Symfony Console - стандарт для построения консольных приложений. Установка через Composer: composer require symfony/console. Пример приложения:

#!/usr/bin/env php
<?php
require __DIR__.'/vendor/autoload.php';
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command
{
    protected static $defaultName = 'greet';
    protected function configure()
    {
        $this->setDescription('Приветствует пользователя')
             ->addArgument('name', InputArgument::REQUIRED, 'Имя пользователя');
    }
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $name = $input->getArgument('name');
        $output->writeln("Hello, $name!");
        return Command::SUCCESS;
    }
}
$app = new Application('MyApp', '1.0');
$app->add(new GreetCommand());
$app->run();

Запуск ./app.php greet John.

Ошибка: если не установлены зависимости Composer, скрипт не запустится. Следует выполнить composer install.

Расширенный пример 1: Парсинг лог-файла с использованием getopt и файловых операций

Пример
#!/usr/bin/env php
<?php
$options = getopt("f:o:", ["file:", "output:", "sort", "reverse"]);
if (empty($options['file']) && empty($options['f'])) {
    fwrite(STDERR, "Не указан входной файл. Используйте --file или -f.\n");
    exit(1);
}
$inFile = $options['file'] ?? $options['f'];
if (!file_exists($inFile)) {
    fwrite(STDERR, "Файл $inFile не найден.\n");
    exit(1);
}
$outFile = $options['output'] ?? $options['o'] ?? 'php://stdout';
$lines = file($inFile, FILE_IGNORE_NEW_LINES);
if (isset($options['sort'])) {
    sort($lines);
}
if (isset($options['reverse'])) {
    $lines = array_reverse($lines);
}
file_put_contents($outFile, implode("\n", $lines));
echo "Готово. Вывод в $outFile\n";
$ ./parse.php --file input.txt --sort --reverse -o output.txt
Готово. Вывод в output.txt

Этот скрипт читает строки из файла, при необходимости сортирует и/или переворачивает, записывает результат. Используется STDERR для сообщения об ошибках.

Расширенный пример 2: Демон с корректной обработкой сигналов

Пример
#!/usr/bin/env php
<?php
// Установка обработчика сигналов
declare(ticks = 1);
$stop = false;
pcntl_signal(SIGTERM, function () use (&$stop) {
    $stop = true;
});
pcntl_signal(SIGINT, function () use (&$stop) {
    $stop = true;
});
// Форк и отсоединение
$pid = pcntl_fork();
if ($pid == -1) die("Ошибка форка");
if ($pid) exit(0);
posix_setsid();
fclose(STDIN); fclose(STDOUT); fclose(STDERR);
fopen('/dev/null', 'r');
$stdout = fopen('/var/log/daemon.log', 'ab');
$stderr = fopen('/var/log/daemon.err', 'ab');
// Основной цикл
while (!$stop) {
    fwrite($stdout, date('Y-m-d H:i:s') . " - Работа демона\n");
    sleep(10);
}
fwrite($stdout, "Демон завершен\n");
exit(0);

Обработчики сигналов меняют флаг, основной цикл проверяет его и корректно завершается. Запускать из командной строки: ./daemon.php </dev/null &

Расширенный пример 3: Параллельная обработка данных с помощью parallel

Пример
#!/usr/bin/env php
<?php
if (!extension_loaded('parallel')) {
    fwrite(STDERR, "Требуется расширение parallel.\n");
    exit(1);
}
$threads = 4;
$tasks = range(1, 20); // задача: вычислить квадрат числа
$worker = function ($num) {
    $result = $num * $num;
    sleep(1); // имитация долгой работы
    return $result;
};
$promises = [];
foreach ($tasks as $num) {
    $promises[] = \parallel\run($worker, [$num]);
}
$results = [];
foreach ($promises as $promise) {
    $results[] = $promise->value();
}
echo "Результаты: ";
print_r($results);
$ ./parallel_example.php
Результаты: Array
(
    [0] => 1
    [1] => 4
    [2] => 9
    ...
    [19] => 400
)

Этот пример демонстрирует использование parallel для параллельного выполнения задач. Каждая задача выполняется в отдельном потоке. Необходимо расширение parallel (устанавливается через pecl install parallel).

Расширенный пример 4: Интерактивное CLI-приложение с readline

Пример
#!/usr/bin/env php
<?php
while (true) {
    $command = readline("MyApp> ");
    if ($command === false) break; // Ctrl+D
    readline_add_history($command);
    if (in_array($command, ['exit', 'quit'])) break;
    if ($command === 'help') {
        echo "Доступные команды: help, echo, date, exit\n";
    } elseif (strpos($command, 'echo ') === 0) {
        echo substr($command, 5) . "\n";
    } elseif ($command === 'date') {
        echo date('Y-m-d H:i:s') . "\n";
    } else {
        echo "Неизвестная команда. Введите help.\n";
    }
}

readline обеспечивает историю комманд и редактирование строки.

Написание скрипта на PHP для Linux - comments

En
скрипт php linux (php)