$argc 与 $argv — CLI 参数
概述
$argc 和 $argv 是 PHP 中专门用于命令行(CLI)模式的预定义变量。$argv 是一个包含命令行参数的数组,$argc 则是该数组的元素个数(argument count)。这两个变量使得 PHP 脚本能够接收和处理来自终端或 cron 任务的外部参数,是编写 PHP CLI 工具、脚本和定时任务的基础。
前置知识
在阅读本节之前,你需要了解:
- PHP CLI 模式的基本概念和运行方式
- 命令行终端的基本操作
- Shell 脚本的参数传递方式
getopt()函数的基本用法
基础概念
$argv 的结构
$argv 是一个索引数组,包含传递给脚本的所有命令行参数:
bash
php script.php alice bob --verbose --output=result.txt对应的 $argv 数组:
$argv = [
0 => 'script.php', // 脚本名称(始终存在)
1 => 'alice', // 第一个参数
2 => 'bob', // 第二个参数
3 => '--verbose', // 第三个参数
4 => '--output=result.txt', // 第四个参数
];
$argc = 5; // 数组元素总数$argc 与 $argv 的关系
| 变量 | 类型 | 说明 |
|---|---|---|
$argv | array | 命令行参数数组,$argv[0] 始终是脚本名称 |
$argc | int | 参数个数,等于 count($argv) |
Web 模式下的可用性
在 Web 模式(Apache/Nginx + PHP-FPM)下,$argv 和 $argc 的行为取决于 register_argc_argv 配置(默认关闭)。在 CLI 模式下它们始终可用。强烈建议仅在 CLI 脚本中使用这两个变量。
语法与代码
基本参数读取
php
<?php
declare(strict_types=1);
// 脚本: greet.php
// 运行: php greet.php Alice 28
// $argv[0] = 'greet.php'
// $argv[1] = 'Alice'
// $argv[2] = '28'
// $argc = 3
if ($argc < 3) {
echo "用法: php {$argv[0]} <姓名> <年龄>" . PHP_EOL;
echo "示例: php {$argv[0]} 张三 25" . PHP_EOL;
exit(1);
}
$name = $argv[1];
$age = (int)$argv[2];
echo "你好, {$name}! 你 {$age} 岁了。" . PHP_EOL;在函数中使用 $argv 和 $argc
php
<?php
declare(strict_types=1);
// $argv 和 $argc 不是超全局变量
// 在函数内需要通过 global 关键字或 $GLOBALS 访问
// 方式一:使用 global 关键字
function getScriptName(): string
{
global $argv;
return $argv[0];
}
// 方式二:使用 $GLOBALS
function getArgCount(): int
{
return $GLOBALS['argc'];
}
// 方式三:作为参数传递(推荐)
function processArgs(array $argv, int $argc): void
{
if ($argc < 2) {
echo "请提供参数" . PHP_EOL;
return;
}
for ($i = 1; $i < $argc; $i++) {
echo "参数 {$i}: {$argv[$i]}" . PHP_EOL;
}
}
processArgs($argv, $argc);使用 getopt() 解析选项
php
<?php
declare(strict_types=1);
/**
* 使用 getopt() 解析命令行选项
*
* 用法: php script.php -f value -b -v --name=value --help
*/
// 短选项: 'f:' (f 后跟冒号表示需要值)
// 'b' (b 不跟冒号表示布尔标志)
// 长选项: ['name:', 'help', 'verbose']
$options = getopt('f:bv', ['name:', 'help', 'verbose']);
// 获取选项值
$file = $options['f'] ?? $options['name'] ?? 'default.txt';
$bool = isset($options['b']);
$verbose = isset($options['v']) || isset($options['verbose']);
$help = isset($options['help']);
if ($help) {
echo "用法: php {$argv[0]} [-f file] [-b] [-v] [--name=value] [--help]" . PHP_EOL;
echo " -f, --name 指定文件名" . PHP_EOL;
echo " -b 启用布尔模式" . PHP_EOL;
echo " -v, --verbose 启用详细输出" . PHP_EOL;
echo " --help 显示帮助信息" . PHP_EOL;
exit(0);
}
echo "文件: {$file}";
echo "布尔: " . ($bool ? 'true' : 'false');
echo "详细: " . ($verbose ? 'true' : 'false');getopt() 选项定义规则
| 格式 | 说明 | 示例 |
|---|---|---|
'a' | 不需要值的短选项 | -a |
'a:' | 必须有值的短选项 | -a value 或 -avalue |
'a::' | 可选值的短选项(PHP 5.3+) | -a 或 -a value |
'abc' | 多个短选项可组合 | -abc 等价于 -a -b -c |
php
<?php
declare(strict_types=1);
// 完整的选项定义示例
$shortOpts = '';
$shortOpts .= 'f:'; // -f <value> 必需值
$shortOpts .= 'v::'; // -v [value] 可选值
$shortOpts .= 'abc'; // -a, -b, -c 布尔标志
$shortOpts .= 'p:'; // -p <value> 必需值
$longOpts = [
'file:', // --file <value> 必需值
'verbose::', // --verbose [value] 可选值
'help', // --help 布尔标志
'output:', // --output <value> 必需值
'dry-run', // --dry-run 布尔标志(含连字符)
];
$options = getopt($shortOpts, $longOpts);
print_r($options);详细说明
getopt() 的局限性
- 选项后的剩余参数:
getopt()只解析选项,不会返回选项之后的非选项参数
php
<?php
declare(strict_types=1);
// 命令行: php script.php --file=test.txt file1.txt file2.txt
// getopt() 只返回 ['file' => 'test.txt']
// file1.txt 和 file2.txt 不会出现在 getopt() 结果中
// 解决方案:手动从 $argv 中提取剩余参数
function getRemainingArgs(array $argv): array
{
// getopt() 会修改 $argv,移除已解析的选项
// 但这个行为在 PHP 不同版本中可能不一致
$remaining = [];
// 更可靠的方式:跳过脚本名和选项
$skipNext = false;
for ($i = 1; $i < count($argv); $i++) {
if ($skipNext) {
$skipNext = false;
continue;
}
if (str_starts_with($argv[$i], '-')) {
// 跳过选项值(如 -f value 或 --file=value)
if (!str_contains($argv[$i], '=') && isset($argv[$i + 1]) && !str_starts_with($argv[$i + 1], '-')) {
$skipNext = true;
}
continue;
}
$remaining[] = $argv[$i];
}
return $remaining;
}- 同一选项多次出现:
getopt()默认只保留最后一个值
php
<?php
declare(strict_types=1);
// 命令行: php script.php --exclude=old --exclude=temp --exclude=backup
// 默认 getopt() 只返回 ['exclude' => 'backup']
// 解决方案:使用独立函数解析多值选项
function parseMultiValueOption(array $argv, string $option): array
{
$values = [];
foreach ($argv as $arg) {
if (str_starts_with($arg, "--{$option}=")) {
$values[] = substr($arg, strlen("--{$option}="));
}
}
return $values;
}
$excludes = parseMultiValueOption($argv, 'exclude');
// ['old', 'temp', 'backup']在 cron 任务中使用 CLI 参数
php
<?php
declare(strict_types=1);
/**
* 数据库备份脚本
*
* Cron 配置示例:
* 0 2 * * * /usr/bin/php /var/www/scripts/backup.php --db=production --compress
*/
$options = getopt('', ['db:', 'compress', 'output:', 'verbose']);
$db = $options['db'] ?? 'default';
$compress = isset($options['compress']);
$output = $options['output'] ?? "/tmp/backup_{$db}_" . date('Ymd') . '.sql';
$verbose = isset($options['verbose']);
echo "开始备份数据库: {$db}" . PHP_EOL;
if ($verbose) {
echo "输出文件: {$output}" . PHP_EOL;
echo "压缩: " . ($compress ? '是' : '否') . PHP_EOL;
}
// 模拟备份逻辑
$sql = "-- Backup of {$db} at " . date('Y-m-d H:i:s') . "\n";
$sql .= "CREATE DATABASE IF NOT EXISTS {$db};\n";
if ($compress && file_put_contents($output . '.gz', gzencode($sql))) {
echo "备份完成(已压缩): {$output}.gz" . PHP_EOL;
} elseif (file_put_contents($output, $sql)) {
echo "备份完成: {$output}" . PHP_EOL;
} else {
echo "备份失败!" . PHP_EOL;
exit(1);
}实战示例
完整的 CLI 命令行工具
php
<?php
declare(strict_types=1);
/**
* 通用 CLI 命令行工具框架
*
* 用法:
* php tool.php <command> [options] [arguments]
*
* 命令:
* list 列出所有项目
* create 创建新项目
* delete 删除项目
* export 导出项目数据
*
* 选项:
* -h, --help 显示帮助
* -v, --verbose 详细输出
* -f, --force 强制执行
* -o, --output 指定输出文件
*/
class CliApp
{
private array $options;
private array $arguments;
private array $commands;
private bool $verbose = false;
public function __construct(array $argv, int $argc)
{
// 解析选项
$this->options = getopt('hvf:o:', ['help', 'verbose', 'force', 'output:']);
// 提取命令和参数
$this->arguments = $this->extractArguments($argv);
// 设置标志
$this->verbose = isset($this->options['v']) || isset($this->options['verbose']);
// 注册命令
$this->registerCommands();
}
public function run(): int
{
// 显示帮助
if (isset($this->options['h']) || isset($this->options['help']) || empty($this->arguments)) {
$this->showHelp();
return 0;
}
$command = $this->arguments[0];
if (!isset($this->commands[$command])) {
echo "错误: 未知命令 '{$command}'" . PHP_EOL;
echo "运行 '{$this->getScriptName()} --help' 查看可用命令" . PHP_EOL;
return 1;
}
return ($this->commands[$command])(array_slice($this->arguments, 1));
}
private function extractArguments(array $argv): array
{
$args = [];
$skipNext = false;
for ($i = 1; $i < count($argv); $i++) {
if ($skipNext) {
$skipNext = false;
continue;
}
$arg = $argv[$i];
// 跳过选项及其值
if (str_starts_with($arg, '-') || str_starts_with($arg, '--')) {
// 检查选项是否需要值(不含 = 的情况)
if (!str_contains($arg, '=') && isset($argv[$i + 1]) && !str_starts_with($argv[$i + 1], '-')) {
$skipNext = true;
}
continue;
}
$args[] = $arg;
}
return $args;
}
private function registerCommands(): void
{
$this->commands = [
'list' => function (array $args): int {
$this->log("列出所有项目:");
foreach (['项目A', '项目B', '项目C'] as $i => $project) {
echo " " . ($i + 1) . ". {$project}" . PHP_EOL;
}
return 0;
},
'create' => function (array $args): int {
$name = $args[0] ?? null;
if ($name === null) {
echo "错误: 请指定项目名称" . PHP_EOL;
return 1;
}
$this->log("创建项目: {$name}");
echo "项目 '{$name}' 创建成功!" . PHP_EOL;
return 0;
},
'delete' => function (array $args): int {
$name = $args[0] ?? null;
$force = isset($this->options['f']) || isset($this->options['force']);
if ($name === null) {
echo "错误: 请指定要删除的项目" . PHP_EOL;
return 1;
}
if (!$force) {
echo "确认删除项目 '{$name}'? (y/n): ";
$confirm = trim(fgets(STDIN));
if (strtolower($confirm) !== 'y') {
echo "已取消" . PHP_EOL;
return 0;
}
}
$this->log("删除项目: {$name}");
return 0;
},
];
}
private function showHelp(): void
{
$script = $this->getScriptName();
echo "用法: php {$script} <命令> [选项] [参数]" . PHP_EOL;
echo PHP_EOL;
echo "命令:" . PHP_EOL;
foreach (array_keys($this->commands) as $cmd) {
echo " {$cmd}" . PHP_EOL;
}
echo PHP_EOL;
echo "选项:" . PHP_EOL;
echo " -h, --help 显示帮助" . PHP_EOL;
echo " -v, --verbose 详细输出" . PHP_EOL;
echo " -f, --force 强制执行" . PHP_EOL;
echo " -o, --output 输出文件" . PHP_EOL;
}
private function getScriptName(): string
{
global $argv;
return basename($argv[0]);
}
private function log(string $message): void
{
if ($this->verbose) {
echo "[LOG] {$message}" . PHP_EOL;
}
}
}
// 运行应用
$app = new CliApp($argv, $argc);
exit($app->run());注意事项
1. Web 模式下不可用
php
<?php
declare(strict_types=1);
// 检测运行模式
if (PHP_SAPI !== 'cli') {
die('此脚本只能在 CLI 模式下运行');
}2. 参数中的特殊字符处理
Shell 会对参数中的特殊字符进行解释,使用引号包裹可避免:
bash
# 不使用引号:Shell 会展开通配符
php script.php *.txt # 实际传递的是展开后的文件名列表
# 使用引号:原样传递
php script.php "*.txt" # 传递字面字符串 *.txt
# 传递含空格的参数
php script.php "hello world" # 正确:传递一个参数 "hello world"
php script.php hello world # 错误:传递两个参数 hello 和 world3. 参数安全性
php
<?php
declare(strict_types=1);
// 始终验证和清理 CLI 参数
$fileName = $argv[1] ?? '';
if (!preg_match('/^[a-zA-Z0-9_\-\.]+$/', $fileName)) {
echo "错误: 文件名包含非法字符" . PHP_EOL;
exit(1);
}最佳实践
- 检测运行模式:始终检查
PHP_SAPI === 'cli' - 提供帮助信息:支持
-h/--help显示用法 - 使用 getopt():对复杂参数使用
getopt()解析 - 参数验证:对所有输入参数进行验证
- 退出码规范:成功返回 0,失败返回非零值
- 错误输出到 stderr:使用
fwrite(STDERR, ...)输出错误信息 - 作为参数传递:将
$argv、$argc作为函数参数,不依赖 global
php
<?php
declare(strict_types=1);
// 最佳实践模板:CLI 脚本
if (PHP_SAPI !== 'cli') {
fwrite(STDERR, "此脚本只能在 CLI 模式下运行\n");
exit(1);
}
$options = getopt('hvf:o:', ['help', 'verbose', 'force', 'output:']);
if (isset($options['h']) || isset($options['help'])) {
echo "用法: php {$argv[0]} [选项] <参数>" . PHP_EOL;
exit(0);
}
$verbose = isset($options['v']) || isset($options['verbose']);
$output = $options['o'] ?? $options['output'] ?? 'php://stdout';
if ($verbose) {
fwrite(STDERR, "详细模式已启用\n");
}下一节
至此,预定义变量部分全部完成。下一节将进入类型系统部分,首先了解 PHP 类型系统的整体架构。