一定要理解这个 PHP 宝石,我花了一段时间才理解它
返回的数组将包含一个布尔值 FALSE,用于已指定的选项。
因为为什么使用 TRUE 来表示“是,它在那里”而可以使用 FALSE 来表示呢?这完全违反直觉,而且肯定只有在 PHP 世界中才有可能。
(PHP 4 >= 4.3.0, PHP 5, PHP 7, PHP 8)
getopt — 从命令行参数列表中获取选项
解析传递给脚本的选项。
short_options
-
) 开头的选项进行匹配。 例如,选项字符串 "x"
识别选项 -x
。 仅允许 a-z、A-Z 和 0-9。 long_options
--
) 开头的选项进行匹配。 例如,longopts 元素 "opt"
识别选项 --opt
。 rest_index
rest_index
参数,则将写入停止解析参数的位置的索引到此变量中。
short_options
参数可能包含以下元素
注意: 可选值不接受
" "
(空格)作为分隔符。
long_options
数组值可能包含
注意:
short_options
和long_options
的格式几乎相同,唯一的区别是long_options
接受选项数组(其中每个元素都是选项),而short_options
接受字符串(其中每个字符都是选项)。
版本 | 描述 |
---|---|
7.1.0 | 添加了 rest_index 参数。 |
示例 #1 getopt() 示例:基础知识
<?php
// 脚本 example.php
$options = getopt("f:hp:");
var_dump($options);
?>
shell> php example.php -fvalue -h
上面的示例将输出
array(2) { ["f"]=> string(5) "value" ["h"]=> bool(false) }
示例 #2 getopt() 示例:介绍长选项
<?php
// 脚本 example.php
$shortopts = "";
$shortopts .= "f:"; // 需要值
$shortopts .= "v::"; // 可选值
$shortopts .= "abc"; // 这些选项不接受值
$longopts = array(
"required:", // 需要值
"optional::", // 可选值
"option", // 无值
"opt", // 无值
);
$options = getopt($shortopts, $longopts);
var_dump($options);
?>
shell> php example.php -f "value for f" -v -a --required value --optional="optional value" --option
上面的示例将输出
array(6) { ["f"]=> string(11) "value for f" ["v"]=> bool(false) ["a"]=> bool(false) ["required"]=> string(5) "value" ["optional"]=> string(14) "optional value" ["option"]=> bool(false) }
示例 #3 getopt() 示例:将多个选项作为一项传递
<?php
// 脚本 example.php
$options = getopt("abc");
var_dump($options);
?>
shell> php example.php -aaac
上面的示例将输出
array(2) { ["a"]=> array(3) { [0]=> bool(false) [1]=> bool(false) [2]=> bool(false) } ["c"]=> bool(false) }
示例 #4 getopt() 示例:使用 rest_index
<?php
// 脚本 example.php
$rest_index = null;
$opts = getopt('a:b:', [], $rest_index);
$pos_args = array_slice($argv, $rest_index);
var_dump($pos_args);
shell> php example.php -a 1 -b 2 -- test
上面的示例将输出
array(1) { [0]=> string(4) "test" }
一定要理解这个 PHP 宝石,我花了一段时间才理解它
返回的数组将包含一个布尔值 FALSE,用于已指定的选项。
因为为什么使用 TRUE 来表示“是,它在那里”而可以使用 FALSE 来表示呢?这完全违反直觉,而且肯定只有在 PHP 世界中才有可能。
有时您希望从命令行和网页同时运行脚本,例如,使用更好的输出进行调试,或者命令行版本将图像写入系统,而网页版本在浏览器中打印图像。 您可以使用此函数来获取相同的选项,无论它们是作为命令行参数传递还是作为 $_REQUEST 值传递。
<?php
/**
* 从命令行或 Web 请求中获取选项
*
* @param string $options
* @param array $longopts
* @return array
*/
function getoptreq ($options, $longopts)
{
if (PHP_SAPI === 'cli' || empty($_SERVER['REMOTE_ADDR'])) // 命令行
{
return getopt($options, $longopts);
}
else if (isset($_REQUEST)) // Web 脚本
{
$found = array();
$shortopts = preg_split('@([a-z0-9][:]{0,2})@i', $options, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
$opts = array_merge($shortopts, $longopts);
foreach ($opts as $opt)
{
if (substr($opt, -2) === '::') // 可选
{
$key = substr($opt, 0, -2);
if (isset($_REQUEST[$key]) && !empty($_REQUEST[$key]))
$found[$key] = $_REQUEST[$key];
else if (isset($_REQUEST[$key]))
$found[$key] = false;
}
else if (substr($opt, -1) === ':') // 必需值
{
$key = substr($opt, 0, -1);
if (isset($_REQUEST[$key]) && !empty($_REQUEST[$key]))
$found[$key] = $_REQUEST[$key];
}
else if (ctype_alnum($opt)) // 无值
{
if (isset($_REQUEST[$opt]))
$found[$opt] = false;
}
}
return $found;
}
return false;
}
?>
示例
<?php
// php script.php -a -c=XXX -e=YYY -f --two --four=ZZZ --five=5
// script.php?a&c=XXX&e=YYY&f&two&four=ZZZ&five=5
$opts = getoptreq('abc:d:e::f::', array('one', 'two', 'three:', 'four:', 'five::'));
var_dump($opts);
/**
array(7) {
'a' => bool(false)
'c' => string(3) "XXX"
'e' => string(3) "YYY"
'f' => bool(false)
'two' => bool(false)
'four' => string(3) "ZZZ"
'five' => string(1) "5"
}
*/
?>
"phpnotes at kipu dot co dot uk" 和 "tim at digicol dot de" 都是错误的或误导性的。Sean 是正确的。在命令行中用引号引起来的包含空格的字符串是一个参数。这与 shell 如何处理命令行有关,而不是 PHP。PHP 的 getopt() 是模仿 Unix/POSIX/C 库 getopt(3) 并可能在其基础上构建的,该库将字符串视为字符串,并且不会在空格处将其拆分。
以下是证明
$ cat opt.php
#! /usr/local/bin/php
<?php
$options = getopt("f:");
print_r($options);
?>
$ opt.php -f a b c
数组
(
[f] => a
)
$ opt.php -f 'a b c'
数组
(
[f] => a b c
)
$ opt.php -f "a b c"
数组
(
[f] => a b c
)
$ opt.php -f a\ b\ c
数组
(
[f] => a b c
)
$
注意,当使用 --dry-run 选项时,此函数对跟随参数的选项可能存在危险,因为存在这种行为
"注意:选项的解析将在第一个非选项找到时结束,任何后续内容都将被丢弃。"
我的脚本正在执行实时运行,即使我将 --dry-run 指定为命令的最后部分,例如 `php foo.php arg1 --dry-run`:getopt() 没有将其包含在其选项列表中,导致我的脚本执行了实时运行。
为了详细说明 'ch1902' 所说的,确实存在需要通过 CLI 和 HTTP 协议执行脚本的情况。在这种情况下,您可以使用以下简化代码规范化脚本如何通过 CLI(使用 getopt())和 HTTP(使用 $_GET)进行解析。
<?php
// 由于新的数组大括号样式,仅限 PHP 5.4+。
function request(array $options = []) {
// 设置默认值。
$defaults = [
'params' => '',
'os' => '',
'username' => posix_getpwuid(posix_geteuid())['name'],
'env' => ''
];
$options += $defaults;
// 对 CLI 的充分检查。
if ('cli' === PHP_SAPI) {
return getopt('', ['params:', 'os::', 'username::', 'env::']) + $options;
}
return $_GET + $options;
}
print_r(request());
?>
上面的代码在通过 CLI 和 HTTP 访问时将产生以下结果。
/**
* params = foo/bar
* username = housni.yakoob
*/
// CLI
$ php script.php --params=foo/bar --username=housni.yakoob
数组
(
[params] => foo/bar
[username] => housni.yakoob
[os] =>
[env] =>
)
// HTTP
script.php?params=foo/bar&username=housni.yakoob
数组
(
[params] => foo/bar
[username] => housni.yakoob
[os] =>
[env] =>
)
/**
* params = foo/bar
* username = 未提供,因此将使用默认值。
*/
// CLI
$ whoami && php script.php --params=foo/bar
housni // <-- 当前用户的用户名(`whoami` 的输出)。
数组
(
[params] => foo/bar
[os] =>
[username] => housni
[env] =>
)
// HTTP
script.php?params=foo/bar
数组
(
[params] => foo/bar
[os] =>
// 我的 Apache 用户的用户名,posix_getpwuid(posix_geteuid())['name'] 的结果
[username] => www-data
[env] =>
)
如您所见,当脚本通过 CLI 或 Web 执行时,输出是一致的。
需要注意的一点是,getopt() 实际上尊重 '--' 选项来结束选项列表。因此,给定代码
test.php
<?php
$options = getopt("m:g:h:");
if (!is_array($options) ) {
print "There was a problem reading in the options.\n\n";
exit(1);
}
$errors = array();
print_r($options);
?>
并运行
# ./test.php ./run_vfs -h test1 -g test2 -m test3 -- this is a test -m green
将返回
数组
(
[h] => test1
[g] => test2
[m] => test3
)
而运行
# /test.php ./run_vfs -h test1 -g test2 -m test3 this is a test -m green
将返回
数组
(
[h] => test1
[g] => test2
[m] => 数组
(
[0] => test3
[1] => green
)
)
有两种更简单(并且快得多)的方法可以获得良好的 getopt() 操作,而无需创建自己的处理程序。
1. 使用 Console_Getopt PEAR 类(应该在大多数 PHP 安装中是标准的),它允许您指定短格式和长格式选项,以及提供给选项的参数是否本身是“可选”的。使用起来非常简单,与编写自己的处理程序相比,需要很少的代码来操作。
2. 如果您无法加载外部 PEAR 对象,请使用 shell 的 getopt() 函数(在 BASH 的情况下,效果很好)来处理选项,然后让 shell 脚本以 PHP 很容易理解的严格参数结构调用您的 PHP 脚本,例如
% myfile.php -a TRUE -b FALSE -c ARGUMENT ...
如果初始参数无效,您可以让 shell 脚本返回错误,而无需调用 PHP 脚本。听起来很复杂,但实际上这是一个非常简单的解决方案,事实上 PHP 自己的 % pear 命令也使用了这种方法。/usr/bin/pear 是一个 shell 脚本,它会在调用 pearcmd.php 并将参数传递给它之前进行一些简单的检查。
第二种方法是迄今为止可移植性最好的方法,因为它允许单个 shell 脚本检查一些事情,比如您的 PHP 版本,并相应地做出反应,例如它是否调用您的 PHP4 或 PHP5 兼容脚本?此外,由于 getopt() 在 Windows 上不可用,因此第二个解决方案允许您将 Windows 特定的测试作为 BAT 文件(而不是 UNIX 上的 BASH、ZSH 或 Korn)来执行。
在 PHP 5.3.0(在 Windows 上)的 getopt() 忽略了一些参数(如果存在语法问题),我决定编写自己的通用参数解析器。
<?php
/**
* 解析 $GLOBALS['argv'] 中的参数并将它们分配到一个数组中。
*
* 支持以下参数类型:
* -e
* -e <value>
* --long-param
* --long-param=<value>
* --long-param <value>
* <value>
*
* @param array $noopt 没有值的参数列表
*/
function parseParameters($noopt = array()) {
$result = array();
$params = $GLOBALS['argv'];
// 可以在此处使用 getopt()(自 PHP 5.3.0 起),但它不能可靠地工作
reset($params);
while (list($tmp, $p) = each($params)) {
if ($p{0} == '-') {
$pname = substr($p, 1);
$value = true;
if ($pname{0} == '-') {
// 长选项 (--<param>)
$pname = substr($pname, 1);
if (strpos($p, '=') !== false) {
// 值在行内指定 (--<param>=<value>)
list($pname, $value) = explode('=', substr($p, 2), 2);
}
}
// 检查下一个参数是描述符还是值
$nextparm = current($params);
if (!in_array($pname, $noopt) && $value === true && $nextparm !== false && $nextparm{0} != '-') list($tmp, $value) = each($params);
$result[$pname] = $value;
} else {
// 参数不属于任何选项
$result[] = $p;
}
}
return $result;
}
?>
例如,以下调用:php.exe -f test.php -- alfons -a 1 -b2 -c --d 2 --e=3=4 --f "alber t" hans wurst
以及在程序中调用 parseParameters(array('f')); 将生成一个结果数组
数组
(
[0] => alfons
[a] => 1
[b2] => 1
[c] => 1
[d] => 2
[e] => 3=4
[f] => 1
[1] => alber t
[2] => hans
[3] => wurst
)
如您所见,没有标识符的值将存储在带数字索引的位置。没有值的现有标识符将获得“true”。
不幸的是,即使使用示例 #4 也不能轻松地检测到错误的 -x 或 --xx 参数,因为最后一个 -x 或 --xx 总是会被“吃掉”,即使它是不正确的。
如果我使用这个(示例 #4)
<?php
// 脚本示例 example.php
$rest_index = null;
$opts = getopt('a:b:', [], $rest_index);
$pos_args = array_slice($argv, $rest_index);
var_dump($pos_args);
?>
shell> php example.php -a 1 -b 2
shell> php example.php -a 1 -b 2 --test
shell> php example.php -a 1 -tb 2
所有这些都返回相同的结果(-t 和 --test 未定义)
array(0) {
}
(最后一个使用组合单个字母,这使得用户测试变得更加复杂)
这里还有另一种从 argv[] 数组中删除 getopt() 找到的选项的方法。它处理不同类型的参数,而不“吃掉”不属于 --option 的部分。(-nr foo param1 param2 foo)
<?php
$parameters = array(
'n' => 'noparam',
'r:' => 'required:',
'o::' => 'optional::',
);
$options = getopt(implode('', array_keys($parameters)), $parameters);
$pruneargv = array();
foreach ($options as $option => $value) {
foreach ($argv as $key => $chunk) {
$regex = '/^'. (isset($option[1]) ? '--' : '-') . $option . '/';
if ($chunk == $value && $argv[$key-1][0] == '-' || preg_match($regex, $chunk)) {
array_push($pruneargv, $key);
}
}
}
while ($key = array_pop($pruneargv)) unset($argv[$key]);
?>
这是我使用 getopt 处理参数的方式:我在程序开头使用 foreach 中的 switch 语句。
<?php
$opts = getopt('hs:');
// 处理命令行参数
foreach (array_keys($opts) as $opt) switch ($opt) {
case 's':
// 使用 s 参数执行操作
$something = $opts['s'];
break;
case 'h':
print_help_message();
exit(1);
}
print "$something\n";
?>
在 PHP 5.3.2 下,如果通过 HTTP 调用 getopt() 而没有任何条件,它似乎会导致脚本无法加载。您需要类似 if(isset($_SERVER['argc'])) $args = getopt(); 的东西来防止这种情况。
如所述,不需要或可选的选项(参数不接受任何值)在作为参数提供时只返回带有 FALSE 值的索引键。这与选项的实际使用方式不符,因此只需利用键的存在并重置该选项的变量索引即可。
<?php
$options = getopt('', ['update', 'insert']);
$options['update'] = array_key_exists('update', $options);
$options['insert'] = array_key_exists('insert', $options);
var_dump($options);
?>
shell> php testfile.php --insert
array(2) {
["update"]=>
bool(false)
["insert"]=>
bool(true)
}
getopt() 只是简单地忽略了 argv 中指定的非必要选项。
很多时候,它在处理命令行错误方面效果不好。
PEAR::Console_Getopt 包可以处理这个问题,但它需要额外的安装。
GNU getopt(1) 在 shell 级别的处理效果很好。
以下是我扩展的 getopt() 可以检测到非必要的选项
<?php
function getoptex($sopt, $lopt, &$ind)
{
global $argv, $argc;
$sopts = getopt($sopt, $lopt);
$sopts_cnt = count($sopts); // 基于原始 sopt 的单选项计数
$asopt = $sopt . implode("", range("a", "z")) . implode("", range("A", "Z")) . implode("", range("0", "9"));
$asopts = getopt($asopt, $lopt);
$asopts_cnt = count($asopts); // 实际单选项计数,包括所有单选项,即使它未在 sopt 中列出
$lopt_trim = array();
foreach ($lopt as $o) {
$lopt_trim[] = trim($o, ":");
}
$alopts_cnt = 0;
$alopts_flag = true;
for ($i = 1; $i < $argc; $i++) {
if ($argv[$i] === "--") { // 选项结束
break;
}
if (strpos($argv[$i], "--") === 0) { // 实际的长选项
$alopts_cnt++;
$o = substr($argv[$i], 2);
if (! in_array($o, $lopt_trim)) { // 但它没有列为 lopt
$alopts_flag = false;
} else {
if (in_array(($o . ":"), $lopt)) { // 如果选项需要值
$i++; // 假设下一个是值
if ($i >= $argc) { // 且值丢失
$alopts_flag = false;
break;
}
}
}
}
}
//var_dump($sopts, $asopts, $lopts, $alopts_cnt, $alopts_flag);
if ($sopts_cnt != $asopts_cnt || (! $alopts_flag)) {
return false;
} else {
return getopt($sopt, $lopt, $ind);
}
}
?>
虽然非常有趣,但 koenbollen at gnospamail dot com 更新的 argv 数组在选项值紧跟在选项之后且无空格的情况下会失败
确实如此
php MyScript.php5 -t5
和
php MyScript.php5 -t 5
在 $options="t:" 中被 getopt 视为相同。
这个升级后的函数应该可以解决这个问题
文件: shift_test.php5
<?php
function shift($options_array)
{
foreach( $options_array as $o => $a )
{
// 在 argv 中查找选项的所有出现并删除找到的选项:
// ----------------------------------------------------------------
// 查找 -o(没有值的简单选项)或 -o<val>(选项和值之间没有空格)的出现:
while($k=array_search("-".$o.$a,$GLOBALS['argv']))
{ // 如果找到,从 argv 中删除:
if($k)
unset($GLOBALS['argv'][$k]);
}
// 查找 -o <val>(选项和值之间有空格)的剩余出现:
while($k=array_search("-".$o,$GLOBALS['argv']))
{ // 如果找到,从 argv 中删除选项和值:
if($k)
{ unset($GLOBALS['argv'][$k]);
unset($GLOBALS['argv'][$k+1]);
}
}
}
// 重新索引:
$GLOBALS['argv']=array_merge($GLOBALS['argv']);
}
print_r($argv);
$options_array=getopt('t:h');
shift($options_array);
print_r($argv);
?>
>php shift_test.php5 -h -t4 param1 param2
将输出
数组
(
[0] => test.php5
[1] => -h
[2] => -t4
[3] => param1
[4] => param2
)
数组
(
[0] => test.php5
[1] => param1
[2] => param2
)
>php shift_test.php5 -h -t 4 param1 param2
将输出
数组
(
[0] => test.php5
[1] => -h
[2] => -t
[3] => 4
[4] => param1
[5] => param2
)
数组
(
[0] => test.php5
[1] => param1
[2] => param2
)
使用 getopt 函数后,您可以使用以下脚本更新 $argv 数组
<?php
$options = "c:ho:s:t:uvV";
$opts = getopt( $options );
foreach( $opts as $o => $a )
{
while( $k = array_search( "-" . $o, $argv ) )
{
if( $k )
unset( $argv[$k] );
if( preg_match( "/^.*".$o.":.*$/i", $options ) )
unset( $argv[$k+1] );
}
}
$argv = array_merge( $argv );
?>
注意:我使用了 array_merge 函数来重新索引数组的键。
干杯,Koen Bollen
如前所述,getopt() 在遇到 '--' 后将停止解析选项。有时您将拥有选项和参数,但用户可能不会始终提供明确的 '--' 选项。
以下是一种快速方法,无论是否使用 '--',都可以一致地收集选项和参数。
#!/usr/bin/php
<?php
$options = getopt('hl::m:v:a', [
'help',
'list::',
'module:',
'version:',
'all',
]);
var_dump( $options );
$args = array_search('--', $argv);
$args = array_splice($argv, $args ? ++$args : (count($argv) - count($opt)));
var_dump( $args );
?>
关于 getopt(String)
使用函数的字符串参数将命令行参数解析为关联数组,因此
* 参数被指定为任何字母后面跟着一个冒号,例如 "h:"。
* 参数以 "h" => "value" 的形式返回。
* 选项被指定为任何字母后面不跟着冒号,例如 "r"。
* 选项以 "r" => (布尔值) false 的形式返回。
还要注意
1) 命令行参数中未传递的选项或参数不会在返回的关联数组中设置。
2) 命令行参数中多次出现的选项或参数将在返回的关联数组中以枚举数组的形式返回。
请注意,如果您在与 getopt() 分开处理 $argv 时,getopt() 期待 $argv 以 $argv[0] 中的命令名称开头。因此,如果您已经移位了 $argv,getopt() 将从第二个标记开始解析,这意味着它将错过第一个选项,或者从第一个选项的值开始解析,从而破坏解析。
$cmd = array_shift($argv);
$key_id=array_shift($argv);
array_unshift($argv,"");
$opts = getopt("a:b:c:");
getopt() 跳过 $argv[0] 以从 $argv[1] 开始解析。$argv[0] 中的内容实际上并不重要。
当使用 -f 选项指示脚本名称时,PHP 不允许使用双破折号 -- 在脚本名称之后定义选项;
例如,以下命令无法执行
php -f myscript.php --config "myconfig.ini"
您对 getopt 的描述要么是错误的,要么是该函数的文档是错误的。
-- 代码片段 --
array getopt ( string $options [, array $longopts [, int &$optind ]] )
--
正如我所读到的,只有第一个参数字符串 $options 是必需的。
-- 代码片段 --
longopts
一个选项数组。每个…
optind
如果 optind 参数存在,…
--
这意味着 "longopts" 是可选的,但第三个参数 "optind" 是必需的(尽管描述留有解释的余地)。这与 PHP 的可能性相矛盾,因为当参数被声明为可选时,所有后续参数也必须被声明为可选的。
参见 https://php.net/manual/en/functions.arguments.php 示例 #4 和 #5。
不可变的嵌入式存根 (PHPStorm) [ https://github.com/JetBrains/phpstorm-stubs/blob/master/standard/standard_3.php ] 指定了这一点
-- 代码片段 --
function getopt ($options, array $longopts = null, &$optind) {}
--
这也是错误的,但可能是基于此文档。
此文档从未真正解释 $short_options 背后的逻辑,只是期望您从阅读示例中理解。
因此,如果您像我一样开始认为 ":" 字符之间的字母顺序有任何意义,请简单点思考。
实际上是…
- 字符串中的每个字母都将被允许作为参数,
- 如果该字母后面有一个冒号,那么它将需要一个值
- 如果它有两个冒号,那么它可以接受一个值
- 字母的顺序完全无关紧要,只要冒号在正确的位置。
这意味着这两个示例是相同的: "ab::c:def:" <-> "adec:f:b::"
> 此函数将返回一个选项/参数对数组,或者在失败时返回 false。
我注意到在我的 PHP 7.4 安装中,getopt 在未指定任何选项的情况下返回一个空数组,而不是文档中所述的 FALSE。虽然 "失败" 比较模糊。也许这并不包括没有选项的情况。
$opts = getopt('', $params = ['ogrn:','inn:','kpp::','host:','port:','user:','pass:','path:','pattern:']) + ['kpp' => 0,'port' => 21,'path' => '/','pattern' => '.+\.dbf'];
array_map(function ($param) use ($opts) {
$matches = [];
if ((bool)preg_match('/(?<param>[^:\s]+)\b:$/sU', $param, $matches)
&& (!array_key_exists($matches['param'], $opts) || empty($opts[$matches['param']])))
die(sprintf('<prtg><error>1</error><text>%s not set</text></text></error></prtg>', $matches['param']));
}, $params);
如果您使用以下命令
php [文件名] [无 (-) 的参数] [选项]
getopt 不会返回值。您可以使用此函数
function getoptions() {
global $argv,$argc;
$options = array();
for($i = 0;$i < $argc;$i++) {
$arg = $argv[$i];
if(strlen($arg) > 1 && $arg[0] == '-') {
if($arg[1] == '-' && $i + 1 < $argc) { $i++; $options[substr($arg,2,strlen($arg))] = $argv[$i]; }
else $options[$arg[1]] = substr($arg,2,strlen($arg));
}
}
return $options;
}