Several hints on writing CLI php scripts
Despite PHP is used mainly for web applications, it's a very handy language for writing command line applications too. Especially if you're used to, it can replace both bash and Perl for you. To demonstrate all of this, let's create a sample backup script.
The requirements
The script's input is a list of directories that should be archived and destination of the archives. For convenience we'll allow two input formats - combination of command line arguments and standard input or configuration file.Message output
First we'll create simple logger class that outputs information and error messages to the appropriate streams: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");
}
}
The main functionality
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");
}
}
Execution of external commands
We'll use tar + gzip to compress our backups. So here's a wrapper function for executing external commands that provides verbose output and option for test run - just output the command without executing it to verify that nothing bad will happen.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));
}
}
Reading arguments from the command line and standard input
Now that the main functionality is ready we'll begin argument parsing. Here the function getopt comes in hand. Our script accepts the following 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
Notice the columns after options expecting argument
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();
Now we can test our script - just run
php backup.php -d /tmp -t
and write a couple of directories, each on separate line. Finish with CTRL+D. You'll see similar output:
[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!
Reading ini files
Now let's complete the last item: reading configuration from ini file. It has the following format:backup_dir=/tmp
dirs[]=/home/foo/Desktop
dirs[]=/home/bar/Documents
dirs[]=/home/xxx
The function that we'll use is 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'];
}
Two sample use cases
Backup all files/folders that start with 'documents' in foo's home folder and put backups in /tmp:find /home/foo -name documents* | php backup.php -d /tmp
Create regular backups on foo and bar's public_html using crontab:
First create config file public_html_backups.ini, containing
backup_dir=/backups
dirs[]=/home/foo/public_html
dirs[]=/home/bar/public_html
Then put the following line in root's crontab:
1 2 * * * php backup.php -c public_html_backups.ini
Now adding another user's public_html is just a matter of edditing config file.
Download full script
No comments yet
This page was last modified on 2024-09-09 12:44:35