請務必理解這個我花了一段時間才理解的 PHP 瑰寶
傳回的陣列將針對已指定的選項包含布林值 FALSE。
因為當您也可以將 FALSE 用於該目的時,為什麼要使用 TRUE 來表示「是的,它在那裡」呢?這完全是違反直覺的,而且肯定只可能在 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。
因為當您也可以將 FALSE 用於該目的時,為什麼要使用 TRUE 來表示「是的,它在那裡」呢?這完全是違反直覺的,而且肯定只可能在 PHP 世界中發生。
有時候,您會想要從命令列和網頁執行指令碼,例如,使用更好的輸出進行偵錯,或使用命令列版本將映像檔寫入系統,但網頁版本會在瀏覽器中列印映像檔。您可以使用此函式取得相同的選項,無論是作為命令列引數或作為 $_REQUEST 值傳遞。
<?php
/**
範例
<?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);
/**
"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() 並未在其選項清單中包含 dry-run,導致我的腳本執行即時執行。
為了詳細說明 '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 或網頁執行腳本時,輸出是一致的。
一個重要的注意事項是,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] => 陣列
(
[0] => test3
[1] => green
)
)
在 PHP5.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
// Script 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]);
?>
有 2 種更簡單 (且速度更快) 的方法可以在不建立自己的處理程式的情況下獲得良好的 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" => "值" 的形式傳回。
* 選項指定為任何字母,後不跟冒號,例如 "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.dev.org.tw/manual/en/functions.arguments.php 範例 #4 和 #5。
不可變的嵌入式 Stub (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 未設定</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;
}