hush: getopts builtin

function                                             old     new   delta
builtin_getopts                                        -     271    +271
bltins1                                              372     384     +12
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 1/0 up/down: 283/0)             Total: 283 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-08-11 01:32:46 +02:00
parent 4628945cd8
commit 74d4058928
7 changed files with 212 additions and 2 deletions

View File

@ -0,0 +1,6 @@
*** no OPTIND, optstring:'we' args:-q -w -e r -t -y
Illegal option -q
var:'?' OPTIND:2
var:'w' OPTIND:3
var:'e' OPTIND:4
exited: var:'?' OPTIND:4

View File

@ -0,0 +1,8 @@
set -- -q -w -e r -t -y
echo "*** no OPTIND, optstring:'we' args:$*"
var=QWERTY
while getopts "we" var; do
echo "var:'$var' OPTIND:$OPTIND"
done
# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
echo "exited: var:'$var' OPTIND:$OPTIND"

View File

@ -48,7 +48,7 @@
* tilde expansion * tilde expansion
* aliases * aliases
* builtins mandated by standards we don't support: * builtins mandated by standards we don't support:
* [un]alias, command, fc, getopts: * [un]alias, command, fc:
* command -v CMD: print "/path/to/CMD" * command -v CMD: print "/path/to/CMD"
* prints "CMD" for builtins * prints "CMD" for builtins
* prints "alias ALIAS='EXPANSION'" for aliases * prints "alias ALIAS='EXPANSION'" for aliases
@ -58,7 +58,6 @@
* (can use this to override standalone shell as well) * (can use this to override standalone shell as well)
* -p: use default $PATH * -p: use default $PATH
* command BLTIN: disables special-ness (e.g. errors do not abort) * command BLTIN: disables special-ness (e.g. errors do not abort)
* getopts: getopt() for shells
* fc -l[nr] [BEG] [END]: list range of commands in history * fc -l[nr] [BEG] [END]: list range of commands in history
* fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
* fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
@ -294,6 +293,11 @@
//config: default y //config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config: //config:
//config:config HUSH_GETOPTS
//config: bool "getopts builtin"
//config: default y
//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
//config:
//config:config HUSH_MEMLEAK //config:config HUSH_MEMLEAK
//config: bool "memleak builtin (debugging)" //config: bool "memleak builtin (debugging)"
//config: default n //config: default n
@ -983,6 +987,9 @@ static int builtin_readonly(char **argv) FAST_FUNC;
static int builtin_fg_bg(char **argv) FAST_FUNC; static int builtin_fg_bg(char **argv) FAST_FUNC;
static int builtin_jobs(char **argv) FAST_FUNC; static int builtin_jobs(char **argv) FAST_FUNC;
#endif #endif
#if ENABLE_HUSH_GETOPTS
static int builtin_getopts(char **argv) FAST_FUNC;
#endif
#if ENABLE_HUSH_HELP #if ENABLE_HUSH_HELP
static int builtin_help(char **argv) FAST_FUNC; static int builtin_help(char **argv) FAST_FUNC;
#endif #endif
@ -1079,6 +1086,9 @@ static const struct built_in_command bltins1[] = {
#if ENABLE_HUSH_JOB #if ENABLE_HUSH_JOB
BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"),
#endif #endif
#if ENABLE_HUSH_GETOPTS
BLTIN("getopts" , builtin_getopts , NULL),
#endif
#if ENABLE_HUSH_HELP #if ENABLE_HUSH_HELP
BLTIN("help" , builtin_help , NULL), BLTIN("help" , builtin_help , NULL),
#endif #endif
@ -9859,6 +9869,69 @@ static int FAST_FUNC builtin_shift(char **argv)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
#if ENABLE_HUSH_GETOPTS
static int FAST_FUNC builtin_getopts(char **argv)
{
/*
TODO:
if a character is followed by a colon, the option is expected to have
an argument, which should be separated from it by white space.
When an option requires an argument, getopts places that argument into
the variable OPTARG.
If an invalid option is seen, getopts places ? into VAR and, if
not silent, prints an error message and unsets OPTARG. If
getopts is silent, the option character found is placed in
OPTARG and no diagnostic message is printed.
If a required argument is not found, and getopts is not silent,
a question mark (?) is placed in VAR, OPTARG is unset, and a
diagnostic message is printed. If getopts is silent, then a
colon (:) is placed in VAR and OPTARG is set to the option
character found.
Test that VAR is a valid variable name?
*/
char cbuf[2];
const char *cp, *optstring, *var;
int c, exitcode;
optstring = *++argv;
if (!optstring || !(var = *++argv)) {
bb_error_msg("usage: getopts OPTSTRING VAR [ARGS]");
return EXIT_FAILURE;
}
cp = get_local_var_value("OPTERR");
opterr = cp ? atoi(cp) : 1;
cp = get_local_var_value("OPTIND");
optind = cp ? atoi(cp) : 0;
/* getopts stops on first non-option. Add "+" to force that */
/*if (optstring[0] != '+')*/ {
char *s = alloca(strlen(optstring) + 2);
sprintf(s, "+%s", optstring);
optstring = s;
}
if (argv[1])
argv[0] = G.global_argv[0]; /* for error messages */
else
argv = G.global_argv;
c = getopt(string_array_len(argv), argv, optstring);
exitcode = EXIT_SUCCESS;
if (c < 0) { /* -1: end of options */
exitcode = EXIT_FAILURE;
c = '?';
}
cbuf[0] = c;
cbuf[1] = '\0';
set_local_var_from_halves(var, cbuf);
set_local_var_from_halves("OPTIND", utoa(optind));
return exitcode;
}
#endif
static int FAST_FUNC builtin_source(char **argv) static int FAST_FUNC builtin_source(char **argv)
{ {
char *arg_path, *filename; char *arg_path, *filename;

View File

@ -0,0 +1,6 @@
*** no OPTIND, optstring:'we' args:-q -w -e r -t -y
./getopt_positional.tests: invalid option -- q
var:'?' OPTIND:2
var:'w' OPTIND:3
var:'e' OPTIND:4
exited: var:'?' OPTIND:4

View File

@ -0,0 +1,8 @@
set -- -q -w -e r -t -y
echo "*** no OPTIND, optstring:'we' args:$*"
var=QWERTY
while getopts "we" var; do
echo "var:'$var' OPTIND:$OPTIND"
done
# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
echo "exited: var:'$var' OPTIND:$OPTIND"

View File

@ -0,0 +1,34 @@
*** no OPTIND, optstring:'ab' args:-a -b c
var:'a' OPTIND:2
var:'b' OPTIND:3
exited: rc:0 var:'?' OPTIND:3
*** OPTIND=1, optstring:'ab' args:-a -b c
var:'a' OPTIND:2
var:'b' OPTIND:3
exited: rc:0 var:'?' OPTIND:3
*** OPTIND=0, optstring:'ab' args:-a -b c
var:'a' OPTIND:2
var:'b' OPTIND:3
exited: rc:0 var:'?' OPTIND:3
*** unset OPTIND, optstring:'ab' args:-a -b c
var:'a' OPTIND:2
var:'b' OPTIND:3
exited: rc:0 var:'?' OPTIND:3
*** optstring:'ab' args:-a -b c
1 rc:0 var:'a' OPTIND:2
2 rc:0 var:'b' OPTIND:3
3 rc:1 var:'?' OPTIND:3
*** unset OPTIND, optstring:'ab' args:-a c -c -b d
var:'a' OPTIND:2
exited: rc:0 var:'?' OPTIND:2
*** unset OPTIND, optstring:'ab' args:-a -c -b d
var:'a' OPTIND:2
./getopt_simple.tests: invalid option -- c
var:'?' OPTIND:3
var:'b' OPTIND:4
exited: rc:0 var:'?' OPTIND:4
*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d
var:'a' OPTIND:2
var:'?' OPTIND:3
var:'b' OPTIND:4
exited: rc:0 var:'?' OPTIND:4

View File

@ -0,0 +1,75 @@
# Simple usage cases for getopts.
#
# OPTIND is either not touched at all (first loop with getopts,
# relying on shell startup init), or getopts state is reset
# before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0".
#
# Each option is a separate argument (no "-abc"). This conceptually
# needs only $OPTIND to hold getopts state.
#
# We check that loop does not stop on unknown option (sets "?"),
# stops on _first_ non-option argument.
echo "*** no OPTIND, optstring:'ab' args:-a -b c"
var=QWERTY
while getopts "ab" var -a -b c; do
echo "var:'$var' OPTIND:$OPTIND"
done
# unfortunately, "rc:0" is shown since while's overall exitcode is "success"
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# Resetting behavior =1
echo "*** OPTIND=1, optstring:'ab' args:-a -b c"
OPTIND=1
while getopts "ab" var -a -b c; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# Resetting behavior =0
echo "*** OPTIND=0, optstring:'ab' args:-a -b c"
OPTIND=0
while getopts "ab" var -a -b c; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# Resetting behavior "unset"
echo "*** unset OPTIND, optstring:'ab' args:-a -b c"
unset OPTIND
while getopts "ab" var -a -b c; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# What is the final exitcode?
echo "*** optstring:'ab' args:-a -b c"
unset OPTIND
getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND"
getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND"
getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND"
# Where would it stop? c or -c?
echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d"
unset OPTIND
while getopts "ab" var -a c -c -b d; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# What happens on unknown option?
echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d"
unset OPTIND
while getopts "ab" var -a -c -b d; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"
# ORTERR=0 suppresses error message?
echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d"
unset OPTIND
OPTERR=0
while getopts "ab" var -a -c -b d; do
echo "var:'$var' OPTIND:$OPTIND"
done
echo "exited: rc:$? var:'$var' OPTIND:$OPTIND"