Няколко трика за писане на CLI PHP скриптовe


Въпреки, че PHP се използва главно за уеб приложения, той е също много удобен за писане на скриптове за командния ред. Особено ако човек е достатъчно навътре, PHP може напълно да замести традиционните фаворити в тази област - bash и Perl. За да демонстрираме това нека разгледаме следния скрипт за архивиране.


Изисквания

Скриптът ще приема списък от файлове и директории, които да се архивират, и директория, където да стоят архивите. За удобство ще използваме два вида за входните данни: комбинация от параметри на командния ред и стандартния вход или файл с конфигурация.

Извеждане на съобщения

За начало създаваме прост клас за съобщения, който извежда грешките на STDERR, а нормалните съобщения - на STDOUT.

class SimpleLogger {

    function info($message) {
        fprintf(STDOUT, date('[Y-m-d H:i:s] ') . $message . "\n");
    }

    function error($message) {
        fprintf(STDERR, date('[Y-m-d H:i:s] ') . $message . "\n");
    }
}

Основната функционалност

class BackupCreator {
    private $dirs;
    private $backup_dir;
    private $test_run;
    private $logger;

    function BackupCreator($dirs, $backup_dir, $test_run=false) {
        $this->dirs = $dirs;
        $this->backup_dir = $backup_dir;
        $this->test_run = $test_run;
        $this->logger = new SimpleLogger();
    }

    function backup() {
        if (!is_dir($this->backup_dir)) {
            $this->logger->error("Directory {$this->backup_dir} does not exist or is not accessible!");
            return -1;
        }

        $backup_timestamp = date("YmdHis");
        foreach ($this->dirs as $dir) {
            if (!file_exists($dir)) {
                $this->logger->error("Backup entry {$dir} does not exist or is not accessible!");
                continue;           
            }

            $archive_name = $this->backup_dir . '/' .$backup_timestamp . str_replace('/', '_', $dir);
            $folder = dirname($dir);
            $item = basename($dir);
            chdir($folder);
            $cmd = "tar -zcf '$archive_name' '$item'";
            $this->execute($cmd);           
        }

        return 0;
    }

    function execute($cmd) {
        $this->logger->info("Executing $cmd");
    }
   
}

Изпълнение на външни програми

Ще използваме комбинация от tar и gzip, за да компресираме архивите. За целта създаваме функция за изпълнение на външни команди, която предлага подробни съобщения и опция за тестово изпълнение - за проверка на изпълняваните команди и предотвратяване на неволни грешки.

    function execute($cmd) {
        $this->logger->info("Executing: $cmd");
        if ($this->test_run) {
            $this->logger->info("Test mode - command is not executed");
            return;
        }
        exec($cmd, $output, $return_value);
        $this->logger->info("Command completed with exit code $return_value");
        if ($output) {
            $this->logger->info("Command output is:\n" . implode("\n", $output));
        }
    }

Четене на аргументите от командния ред и стандартния вход

След като имаме основната функционалност започваме с четенето на аргументите. За целта ползваме функцията getopt. Нашият скрипт ще приема следните опции:
    -d <dir> - директорията за архивите е <dir>, а самите файлове и директории за архивиране се четат от стандартния вход STDIN, по един на ред
    -c <file> - използва се конфигурационен файл (това е с по-висок приоритет от -d)
    -t - тестово изпълнение

Обърнете внимание на двоеточията след опциите, които очакват аргумент.

function usage() {
    echo "Available options:
    -d <dir> - location of backup dir is <dir>, and backup items list is read from STDIN, one on each line
    -c <file> - use configuration file (this option overrides -d)
    -t - test run
";
    exit(-1);
}

$opts = getopt('d:c:t');
if (!$opts) {
    usage();
}

$dirs = array();
$backup_dir = '';
$test_run = isset($opts['t']);
if (isset($opts['c']) && $opts['c']) {
    //TODO
} else if (isset($opts['d']) && $opts['d']) {
    $backup_dir = $opts['d'];
    while ($item = fgets(STDIN)) {
        $dirs[] = trim($item);   
    }
} else {
    usage();
}

if (!$dirs || !$backup_dir) {
    usage();
}

$backup = new BackupCreator($dirs, $backup_dir, $test_run);
$backup->backup();

Сега можем да опитаме скрипта. Изпълнете:

    php backup.php -d /tmp -t

и въведете няколко файла и директории, всеки на отделен ред. Накрая натиснете CTRL+D. Получава се подобен изход:

[2010-02-02 01:42:19] Executing: tar -zcf '/tmp/20100202014219_home_foo_Desktop' 'Desktop'
[2010-02-02 01:42:19] Test mode - command is not executed
[2010-02-02 01:42:19] Backup entry /home/xxx does not exist or is not accessible!

Четене на ini файл

Сега последната част: четене на входа от конфигурационен файл. Форматът е следния:

backup_dir=/tmp

dirs[]=/home/foo/Desktop
dirs[]=/home/bar/Documents
dirs[]=/home/xxx

Тук използваме функцията parse_ini_file

if (isset($opts['c']) && $opts['c']) {
    $config = parse_ini_file($opts['c'], true);
    if (!$config) {
        fprintf(STDERR, "Error while parsing config file {$opts['c']}");
        exit(-1);
    }
    $backup_dir = $config['backup_dir'];
    $dirs = $config['dirs'];
}

Два примера за използване

Архивиране на всички файлове и директории, които започват с 'documents' в домашната папка на foo, като архивите отиват в /tmp:

find /home/foo -name documents* | php backup.php -d /tmp


Създаване на ежедневни архиви на папките public_html на потребителите foo и bar. Първо създаваме конфигурационен файл public_html_backups.ini:

backup_dir=/backups
dirs[]=/home/foo/public_html
dirs[]=/home/bar/public_html


След това добавяме следното в crontab на root:

1 2 * * * php backup.php -c public_html_backups.ini

Удобното тук е, че добавянето на нов потребител е само въпрос на редактиране на конфигурационния файл, без промени по кода и crontab


Свалете целия скрипт

Няма коментари

Обратно към списъка със статиите

Тази страница последно е променяна на 2024-04-19 07:06:05