务必理解这个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 "读取选项时出现问题。\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] => Array
(
[0] => test3
[1] => green
)
)
在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) {
}
(最后一个例子使用单个字母组合,使得用户测试变得更加复杂)
在PHP 5.3.2下,如果通过HTTP调用getopt()函数且没有任何条件,则会使脚本加载失败。你需要类似if(isset($_SERVER['argc'])) $args = getopt();
这样的代码来防止这种情况。
这就是我如何使用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";
?>
这是另一种从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()操作,而无需创建自己的处理程序。
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)来执行。
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)
使用函数的String参数指定参数和选项,将命令行参数解析为关联数组,因此
* 参数指定为任何字母后跟一个冒号,例如“h:”。
* 参数返回为“h”=>“value”。
* 选项指定为任何字母后不跟冒号,例如“r”。
* 选项返回为“r”=>(boolean)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]中的内容实际上并不重要。
如前所述,非必需或可选的选项(参数不接受任何值)仅在作为参数提供时返回索引键的值为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)
}
当使用-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;
}