PHP命令行脚本开发实战
PHP命令行脚本开发实战
PHP不仅用来做Web开发,写命令行脚本也很方便。定时任务、数据处理、队列消费,这些场景用PHP命令行模式再合适不过了。
先说说命令行参数的处理。$argc是参数个数,$argv是参数数组。getopt可以解析选项参数。
```php
// 命令行参数解析
echo "脚本名: {$argv[0]}\n";
echo "参数个数: $argc\n";
// 手动解析参数
for ($i = 1; $i < $argc; $i++) {
echo "参数{$i}: {$argv[$i]}\n";
}
// 使用getopt解析选项
$options = getopt('u:p:h:', ['host:', 'port:', 'help', 'verbose']);
if (isset($options['help'])) {
echo "用法: php script.php -u <用户名> -p <密码> [--host=<主机>]\n";
echo "选项:\n";
echo " -u 用户名\n";
echo " -p 密码\n";
echo " -h 主机\n";
echo " --port 端口\n";
echo " --verbose 详细输出\n";
exit(0);
}
$username = $options['u'] ?? null;
$password = $options['p'] ?? null;
$host = $options['h'] ?? $options['host'] ?? 'localhost';
$port = $options['port'] ?? 3306;
$verbose = isset($options['verbose']);
if ($verbose) {
echo "详细模式: 连接 $host:$port\n";
}
?>
```
命令行输出可以加上颜色,让信息更醒目。
```php
class Console
{
const COLORS = [
'red' => "\033[31m",
'green' => "\033[32m",
'yellow' => "\033[33m",
'blue' => "\033[34m",
'magenta' => "\033[35m",
'cyan' => "\033[36m",
'white' => "\033[37m",
'reset' => "\033[0m",
];
const STYLES = [
'bold' => "\033[1m",
'dim' => "\033[2m",
'italic' => "\033[3m",
'underline' => "\033[4m",
'blink' => "\033[5m",
];
public static function text(string $text, string $color = 'white'): string
{
$colorCode = self::COLORS[$color] ?? self::COLORS['white'];
return $colorCode . $text . self::COLORS['reset'];
}
public static function info(string $message): void
{
echo self::COLORS['green'] . '[INFO] ' . self::COLORS['reset']
. $message . "\n";
}
public static function error(string $message): void
{
echo self::COLORS['red'] . '[ERROR] ' . self::COLORS['reset']
. $message . "\n";
}
public static function warning(string $message): void
{
echo self::COLORS['yellow'] . '[WARNING] ' . self::COLORS['reset']
. $message . "\n";
}
public static function success(string $message): void
{
echo self::COLORS['green'] . '[SUCCESS] ' . $message . self::COLORS['reset'] . "\n";
}
public static function table(array $headers, array $rows): void
{
// 计算列宽
$widths = [];
foreach ($headers as $i => $header) {
$widths[$i] = strlen($header);
foreach ($rows as $row) {
$widths[$i] = max($widths[$i], strlen((string)($row[$i] ?? '')));
}
}
// 输出表头
$separator = '+';
foreach ($widths as $w) {
$separator .= str_repeat('-', $w + 2) . '+';
}
echo $separator . "\n";
echo '|';
foreach ($headers as $i => $header) {
echo ' ' . str_pad($header, $widths[$i]) . ' |';
}
echo "\n";
echo $separator . "\n";
// 输出数据行
foreach ($rows as $row) {
echo '|';
foreach ($row as $i => $cell) {
echo ' ' . str_pad((string)$cell, $widths[$i]) . ' |';
}
echo "\n";
}
echo $separator . "\n";
}
public static function progressBar(int $current, int $total, int $width = 50): void
{
$percent = round($current / $total * 100);
$filled = round($width * $current / $total);
$bar = str_repeat('=', $filled) . str_repeat(' ', $width - $filled);
printf("\r进度: [%s] %d%% (%d/%d)", $bar, $percent, $current, $total);
if ($current === $total) {
echo "\n";
}
}
public static function confirm(string $message): bool
{
echo self::COLORS['yellow'] . "$message (y/N): " . self::COLORS['reset'];
$input = trim(fgets(STDIN));
return strtolower($input) === 'y' || strtolower($input) === 'yes';
}
public static function input(string $prompt, string $default = ''): string
{
$defaultText = $default ? " [$default]" : '';
echo "$prompt$defaultText: ";
$input = trim(fgets(STDIN));
return $input ?: $default;
}
}
Console::info('系统启动');
Console::warning('磁盘空间不足');
Console::error('连接失败');
Console::success('操作完成');
echo Console::text('红色文字', 'red') . "\n";
echo Console::text('蓝色粗体', 'blue') . "\n";
Console::table(
['ID', '姓名', '年龄', '城市'],
[
[1, '张三', 28, '北京'],
[2, '李四', 35, '上海'],
[3, '王五', 22, '广州'],
]
);
for ($i = 1; $i <= 100; $i++) {
Console::progressBar($i, 100);
usleep(30000);
}
?>
```
管线和重定向让命令行脚本可以组合使用。
```php
// 从标准输入读取
$input = file_get_contents('php://stdin');
$lines = explode("\n", trim($input));
echo "读取了 " . count($lines) . " 行数据\n";
// 处理每行数据
$processed = array_map(function ($line) {
$line = trim($line);
if (empty($line)) return null;
return strtoupper($line);
}, $lines);
$processed = array_filter($processed);
// 输出到标准输出
foreach ($processed as $item) {
echo $item . "\n";
}
// 输出到标准错误
fwrite(STDERR, "处理完成\n");
?>
```
命令行脚本的生命周期管理和错误处理比Web脚本更重要。
```php
// 信号处理
declare(ticks = 1);
pcntl_signal(SIGTERM, 'signalHandler');
pcntl_signal(SIGINT, 'signalHandler');
$running = true;
function signalHandler(int $signal): void
{
global $running;
echo "\n收到信号: $signal\n";
echo "正在清理...\n";
$running = false;
}
// 内存管理和超时控制
set_time_limit(0); // CLI模式通常不限执行时间
$startTime = time();
$maxRuntime = 3600; // 最长运行1小时
echo "开始处理...\n";
$count = 0;
while ($running) {
$count++;
// 检查运行时间
if (time() - $startTime > $maxRuntime) {
echo "运行超时\n";
break;
}
// 检查内存
if (memory_get_usage(true) > 100 * 1024 * 1024) {
echo "内存超限\n";
break;
}
// 模拟处理
if ($count > 1000) {
$running = false;
echo "处理完成\n";
}
}
echo "总共处理: $count 条\n";
?>
```
定时任务也是命令行脚本的常见用途。
```php
// 简单定时任务调度器
class Scheduler
{
private array $tasks = [];
public function addTask(string $name, callable $task, int $interval): void
{
$this->tasks[] = [
'name' => $name,
'task' => $task,
'interval' => $interval,
'last_run' => 0,
];
}
public function run(): void
{
echo "调度器启动\n";
while (true) {
$now = time();
foreach ($this->tasks as &$task) {
if ($now - $task['last_run'] >= $task['interval']) {
$task['last_run'] = $now;
echo "执行任务: {$task['name']}\n";
try {
($task['task'])();
} catch (Exception $e) {
echo "任务失败: {$task['name']}: {$e->getMessage()}\n";
}
}
}
unset($task);
sleep(1);
}
}
}
$scheduler = new Scheduler();
$scheduler->addTask('数据库备份', function () {
echo "备份完成\n";
}, 3600);
$scheduler->addTask('清理日志', function () {
echo "日志清理完成\n";
}, 7200);
?>
```
命令行脚本在PHP开发中占有一席之地。定时任务用cron加PHP脚本,数据处理用管道重定向,队列消费用守护进程模式。掌握了命令行开发,PHP的应用场景就不局限于Web了。
