From 74d40589288890fffea437ed2f38ad1f2dc740e5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 01:32:46 +0200 Subject: [PATCH] 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 --- .../ash-getopts/getopt_positional.right | 6 ++ .../ash-getopts/getopt_positional.tests | 8 ++ shell/hush.c | 77 ++++++++++++++++++- .../hush-getopts/getopt_positional.right | 6 ++ .../hush-getopts/getopt_positional.tests | 8 ++ .../hush-getopts/getopt_simple.right | 34 ++++++++ .../hush-getopts/getopt_simple.tests | 75 ++++++++++++++++++ 7 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-getopts/getopt_positional.right create mode 100755 shell/ash_test/ash-getopts/getopt_positional.tests create mode 100644 shell/hush_test/hush-getopts/getopt_positional.right create mode 100755 shell/hush_test/hush-getopts/getopt_positional.tests create mode 100644 shell/hush_test/hush-getopts/getopt_simple.right create mode 100755 shell/hush_test/hush-getopts/getopt_simple.tests diff --git a/shell/ash_test/ash-getopts/getopt_positional.right b/shell/ash_test/ash-getopts/getopt_positional.right new file mode 100644 index 000000000..37d0ec845 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.right @@ -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 diff --git a/shell/ash_test/ash-getopts/getopt_positional.tests b/shell/ash_test/ash-getopts/getopt_positional.tests new file mode 100755 index 000000000..a5404a2a0 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.tests @@ -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" diff --git a/shell/hush.c b/shell/hush.c index b53d1dcfb..dba12c12e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -48,7 +48,7 @@ * tilde expansion * aliases * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts: + * [un]alias, command, fc: * command -v CMD: print "/path/to/CMD" * prints "CMD" for builtins * prints "alias ALIAS='EXPANSION'" for aliases @@ -58,7 +58,6 @@ * (can use this to override standalone shell as well) * -p: use default $PATH * 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 [-e EDITOR] [BEG] [END]: edit/rerun range of commands * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP @@ -294,6 +293,11 @@ //config: default y //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //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: bool "memleak builtin (debugging)" //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_jobs(char **argv) FAST_FUNC; #endif +#if ENABLE_HUSH_GETOPTS +static int builtin_getopts(char **argv) FAST_FUNC; +#endif #if ENABLE_HUSH_HELP static int builtin_help(char **argv) FAST_FUNC; #endif @@ -1079,6 +1086,9 @@ static const struct built_in_command bltins1[] = { #if ENABLE_HUSH_JOB BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), #endif +#if ENABLE_HUSH_GETOPTS + BLTIN("getopts" , builtin_getopts , NULL), +#endif #if ENABLE_HUSH_HELP BLTIN("help" , builtin_help , NULL), #endif @@ -9859,6 +9869,69 @@ static int FAST_FUNC builtin_shift(char **argv) 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) { char *arg_path, *filename; diff --git a/shell/hush_test/hush-getopts/getopt_positional.right b/shell/hush_test/hush-getopts/getopt_positional.right new file mode 100644 index 000000000..f1c942476 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.right @@ -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 diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests new file mode 100755 index 000000000..a5404a2a0 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.tests @@ -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" diff --git a/shell/hush_test/hush-getopts/getopt_simple.right b/shell/hush_test/hush-getopts/getopt_simple.right new file mode 100644 index 000000000..b4855fa1a --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.right @@ -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 diff --git a/shell/hush_test/hush-getopts/getopt_simple.tests b/shell/hush_test/hush-getopts/getopt_simple.tests new file mode 100755 index 000000000..8615ae366 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.tests @@ -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" -- 2.25.1