模擬 PHP 的 getopt()
Aug. 16, 2008 / All(全部), Digitalk, 我寫(改)的程式 / Author: 可愛柚爸
PHP 的 getopt() 可以抓取命令列的參數,但是很不幸的,目前的 PHP 5.2.6 還沒有完整支援 getopt() 的所有功能,比如說 long options 就不支援,要等到 PHP 5.3.0 才有完整的支援。但是我最近寫程式的時候要用到這種功能,又等不及 5.3.0,就只好自己寫了一個 _getopt() 來代替。
PHP 的 getopt() 參數用法可以參考他們的網頁 http://us.php.net/manual/en/function.getopt.php ,基本上我是完全模擬,所以所有參數值和使用方法都和該網頁所寫的一模一樣,這樣以後如果有必要轉換到 5.3.0 的 getopt() 的話,也比較不痛。
實際上 _getopt() 不但完全相容於 getopt(),還多了一個功能,就是可以選擇使用自己的參數列,而不使用命令列的參數,詳情請見程式碼裡的註解。
為了 _getopt(),我另外還寫了一個 split_para() 來分解參數列,請一併享用。
function _getopt ( ) {
/* _getopt(): Ver. 1.3 2009/05/30
My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
Usage: _getopt ( [$flag,] $short_option [, $long_option] );
Note that another function split_para() is required, which can be found in the same
page.
_getopt() fully simulates getopt() which is described at
http://us.php.net/manual/en/function.getopt.php , including long options for PHP
version under 5.3.0. (Prior to 5.3.0, long options was only available on few systems)
Besides legacy usage of getopt(), I also added a new option to manipulate your own
argument lists instead of those from command lines. This new option can be a string
or an array such as
$flag = "-f value_f -ab --required 9 --optional=PK --option -v test -k";
or
$flag = array ( "-f", "value_f", "-ab", "--required", "9", "--optional=PK", "--option" );
So there are four ways to work with _getopt(),
1. _getopt ( $short_option );
it's a legacy usage, same as getopt ( $short_option ).
2. _getopt ( $short_option, $long_option );
it's a legacy usage, same as getopt ( $short_option, $long_option ).
3. _getopt ( $flag, $short_option );
use your own argument lists instead of command line arguments.
4. _getopt ( $flag, $short_option, $long_option );
use your own argument lists instead of command line arguments.
*/
if ( func_num_args() == 1 ) {
$flag = $flag_array = $GLOBALS['argv'];
$short_option = func_get_arg ( 0 );
$long_option = array ();
} else if ( func_num_args() == 2 ) {
if ( is_array ( func_get_arg ( 1 ) ) ) {
$flag = $GLOBALS['argv'];
$short_option = func_get_arg ( 0 );
$long_option = func_get_arg ( 1 );
} else {
$flag = func_get_arg ( 0 );
$short_option = func_get_arg ( 1 );
$long_option = array ();
}
} else if ( func_num_args() == 3 ) {
$flag = func_get_arg ( 0 );
$short_option = func_get_arg ( 1 );
$long_option = func_get_arg ( 2 );
} else {
exit ( "wrong options\n" );
}
$short_option = trim ( $short_option );
$short_no_value = array();
$short_required_value = array();
$short_optional_value = array();
$long_no_value = array();
$long_required_value = array();
$long_optional_value = array();
$options = array();
for ( $i = 0; $i < strlen ( $short_option ); ) {
if ( $short_option{$i} != ":" ) {
if ( $i == strlen ( $short_option ) - 1 ) {
$short_no_value[] = $short_option{$i};
break;
} else if ( $short_option{$i+1} != ":" ) {
$short_no_value[] = $short_option{$i};
$i++;
continue;
} else if ( $short_option{$i+1} == ":" && $short_option{$i+2} != ":" ) {
$short_required_value[] = $short_option{$i};
$i += 2;
continue;
} else if ( $short_option{$i+1} == ":" && $short_option{$i+2} == ":" ) {
$short_optional_value[] = $short_option{$i};
$i += 3;
continue;
}
} else {
continue;
}
}
foreach ( $long_option as $a ) {
if ( substr( $a, -2 ) == "::" ) {
$long_optional_value[] = substr( $a, 0, -2);
continue;
} else if ( substr( $a, -1 ) == ":" ) {
$long_required_value[] = substr( $a, 0, -1 );
continue;
} else {
$long_no_value[] = $a;
continue;
}
}
if ( is_array ( $flag ) )
$flag_array = $flag;
else {
$flag = "- $flag";
$flag_array = split_para( $flag );
}
for ( $i = 0; $i < count( $flag_array ); ) {
if ( $i >= count ( $flag_array ) )
break;
if ( ! $flag_array[$i] || $flag_array[$i] == "-" ) {
$i++;
continue;
}
if ( $flag_array[$i]{0} != "-" ) {
$i++;
continue;
}
if ( substr( $flag_array[$i], 0, 2 ) == "--" ) {
if (strpos($flag_array[$i], '=') != false) {
list($key, $value) = explode('=', substr($flag_array[$i], 2), 2);
if ( in_array ( $key, $long_required_value ) || in_array ( $key, $long_optional_value ) )
$options[$key][] = $value;
$i++;
continue;
}
if (strpos($flag_array[$i], '=') == false) {
$key = substr( $flag_array[$i], 2 );
if ( in_array( substr( $flag_array[$i], 2 ), $long_required_value ) ) {
$options[$key][] = $flag_array[$i+1];
$i += 2;
continue;
} else if ( in_array( substr( $flag_array[$i], 2 ), $long_optional_value ) ) {
if ( $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
$options[$key][] = $flag_array[$i+1];
$i += 2;
} else {
$options[$key][] = FALSE;
$i ++;
}
continue;
} else if ( in_array( substr( $flag_array[$i], 2 ), $long_no_value ) ) {
$options[$key][] = FALSE;
$i++;
continue;
} else {
$i++;
continue;
}
}
} else if ( $flag_array[$i]{0} == "-" && $flag_array[$i]{1} != "-" ) {
for ( $j=1; $j < strlen($flag_array[$i]); $j++ ) {
if ( in_array( $flag_array[$i]{$j}, $short_required_value ) || in_array( $flag_array[$i]{$j}, $short_optional_value )) {
if ( $j == strlen($flag_array[$i]) - 1 ) {
if ( in_array( $flag_array[$i]{$j}, $short_required_value ) ) {
$options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
$i += 2;
} else if ( in_array( $flag_array[$i]{$j}, $short_optional_value ) && $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
$options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
$i += 2;
} else {
$options[$flag_array[$i]{$j}][] = FALSE;
$i ++;
}
$plus_i = 0;
break;
} else {
$options[$flag_array[$i]{$j}][] = substr ( $flag_array[$i], $j + 1 );
$i ++;
$plus_i = 0;
break;
}
} else if ( in_array ( $flag_array[$i]{$j}, $short_no_value ) ) {
$options[$flag_array[$i]{$j}][] = FALSE;
$plus_i = 1;
continue;
} else {
$plus_i = 1;
break;
}
}
$i += $plus_i;
continue;
}
$i++;
continue;
}
foreach ( $options as $key => $value ) {
if ( count ( $value ) == 1 ) {
$options[ $key ] = $value[0];
}
}
return $options;
}
function split_para ( $pattern ) {
/* split_para() version 1.0 2008/08/19
My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
This function is to parse parameters and split them into smaller pieces.
preg_split() does similar thing but in our function, besides "space", we
also take the three symbols " (double quote), '(single quote),
and \ (backslash) into consideration because things in a pair of " or '
should be grouped together.
As an example, this parameter list
-f "test 2" -ab --required "t\"est 1" --optional="te'st 3" --option -v 'test 4'
will be splited into
-f
t"est 2
-ab
--required
test 1
--optional=te'st 3
--option
-v
test 4
see the code below,
$pattern = "-f \"test 2\" -ab --required \"t\\\"est 1\" --optional=\"te'st 3\" --option -v 'test 4'";
$result = split_para( $pattern );
echo "ORIGINAL PATTERN: $pattern\n\n";
var_dump( $result );
*/
$begin=0;
$backslash = 0;
$quote = "";
$quote_mark = array();
$result = array();
$pattern = trim ( $pattern );
for ( $end = 0; $end < strlen ( $pattern ) ; ) {
if ( ! in_array ( $pattern{$end}, array ( " ", "\"", "'", "\\" ) ) ) {
$backslash = 0;
$end ++;
continue;
}
if ( $pattern{$end} == "\\" ) {
$backslash++;
$end ++;
continue;
} else if ( $pattern{$end} == "\"" ) {
if ( $backslash % 2 == 1 || $quote == "'" ) {
$backslash = 0;
$end ++;
continue;
}
if ( $quote == "" ) {
$quote_mark[] = $end - $begin;
$quote = "\"";
} else if ( $quote == "\"" ) {
$quote_mark[] = $end - $begin;
$quote = "";
}
$backslash = 0;
$end ++;
continue;
} else if ( $pattern{$end} == "'" ) {
if ( $backslash % 2 == 1 || $quote == "\"" ) {
$backslash = 0;
$end ++;
continue;
}
if ( $quote == "" ) {
$quote_mark[] = $end - $begin;
$quote = "'";
} else if ( $quote == "'" ) {
$quote_mark[] = $end - $begin;
$quote = "";
}
$backslash = 0;
$end ++;
continue;
} else if ( $pattern{$end} == " " ) {
if ( $quote != "" ) {
$backslash = 0;
$end ++;
continue;
} else {
$backslash = 0;
$cand = substr( $pattern, $begin, $end-$begin );
for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
if ( in_array ( $j, $quote_mark ) )
continue;
$cand1 .= $cand{$j};
}
if ( $cand1 ) {
eval( "\$cand1 = \"$cand1\";" );
$result[] = $cand1;
}
$quote_mark = array();
$cand1 = "";
$end ++;
$begin = $end;
continue;
}
}
}
$cand = substr( $pattern, $begin, $end-$begin );
for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
if ( in_array ( $j, $quote_mark ) )
continue;
$cand1 .= $cand{$j};
}
eval( "\$cand1 = \"$cand1\";" );
if ( $cand1 )
$result[] = $cand1;
return $result;
}
The following line is giving me a problem:
if ( $short_option{$i+1} != “:” ) {
If $short_options is $i-chars-long, it can’t get the char $i+1 and outputs the following message:
Notice: Uninitialized string offset: 11 in /path/php-getopt.inc.php on line 78
I’ve tried with a few methods but neither worked. And it can’t be hidden with @.
Any idea?
Cheers,
– Josso
Please email me via http://whspr.me/1mJ (will be deactivated after 31 days) cause I’d possibly won’t come by here soon. :p
Short options look like “f:v::abc”, did you use a correct syntax?
For a further investigation, please let me know your complete options of _getopt(). I will try it on my box.
I used your example, from PHP.net/getopt.
The code itself is working fine – it just throws an error message.
“atnak” describes it in a PHP.net-comment: http://php.net/manual/en/language.types.string.php#41470
Cheers,
OK I see what happened.
It’s not critical at all. That message means that the program intends to access a position beyond the string(illegal location). In C/C++ we will get a message “null pointer assignment” but in php there’s nothing more than a runtime error.
However if you do care, try below fix please,
change original lines
for ( $i = 0; $i < strlen ( $short_option ); ) {
if ( $short_option{$i} != “:” ) {
if ( $short_option{$i+1} != “:” ) {
$short_no_value[] = $short_option{$i};
$i++;
continue;
}
to below,
for ( $i = 0; $i < strlen ( $short_option ); ) {
if ( $short_option{$i} != “:” ) {
if ( $i == strlen ( $short_option ) – 1 ) {
$short_no_value[] = $short_option{$i};
break;
} else if ( $short_option{$i+1} != “:” ) {
$short_no_value[] = $short_option{$i};
$i++;
continue;
}
The new codes will check length of $short_option first and prevent $i+1 from running over it.
Good luck!
Thanks, man!
Works perfectly and is now valid when E_ALL is on.
Cheers,
– Johan aka “Josso”
My honor
I have got a licensing question: Is it okay to use the code in a GPLed project?
To hakre:
Just checked php site, the latest php version is 5.3.1. I think you can enjoy all features of getopt() in this new release.