diff options
author | Elliott Hughes <enh@google.com> | 2019-11-21 14:09:23 -0800 |
---|---|---|
committer | Rob Landley <rob@landley.net> | 2019-11-22 06:54:31 -0600 |
commit | c77b66455762f42bb824c1aa8cc60e7f4d44bdab (patch) | |
tree | 72a44d951b912d43290ef0ee6bc705c2da0f2876 | |
parent | 176c6fa4580571d262434ca2d610c860d30cf876 (diff) | |
download | toybox-c77b66455762f42bb824c1aa8cc60e7f4d44bdab.tar.gz |
Add getopt(1).
Includes new tests.
-rw-r--r-- | lib/portability.h | 3 | ||||
-rwxr-xr-x | tests/getopt.test | 44 | ||||
-rw-r--r-- | toys/pending/getopt.c | 104 |
3 files changed, 151 insertions, 0 deletions
diff --git a/lib/portability.h b/lib/portability.h index b586c132..481e3f04 100644 --- a/lib/portability.h +++ b/lib/portability.h @@ -105,6 +105,9 @@ static inline char *basename(char *path) { return __xpg_basename(path); } char *strcasestr(const char *haystack, const char *needle); #endif // defined(glibc) +// getopt_long(), getopt_long_only(), and struct option. +#include <getopt.h> + #if !defined(__GLIBC__) // POSIX basename. #include <libgen.h> diff --git a/tests/getopt.test b/tests/getopt.test new file mode 100755 index 00000000..4c694581 --- /dev/null +++ b/tests/getopt.test @@ -0,0 +1,44 @@ +#!/bin/bash + +[ -f testing.sh ] && . testing.sh + +#testing "name" "command" "result" "infile" "stdin" + +function test_getopt() { + testcmd "$1" "$1" "$2\n" "" "" +} + +# Traditional behavior was to take the first argument as OPTSTR and not quote. +test_getopt "a b c" " -- b c" +test_getopt "a -a b c" " -a -- b c" +test_getopt "a -- -a b c" " -- -a b c" + +# Modern -o mode. +test_getopt "-o a -- " " --" +test_getopt "-o a -- -a b c" " -a -- 'b' 'c'" +test_getopt "-o a: -- -a b c" " -a 'b' -- 'c'" + +# Long options (--like --this). +test_getopt "-o a -l long -- -a --long a" " -a --long -- 'a'" +test_getopt "-o a -l one -l two -- -a --one --two" " -a --one --two --" +test_getopt "-o a -l one,two -- -a --one --two" " -a --one --two --" +# -l arg: (required) +test_getopt "-o a -l one: -- -a --one arg" " -a --one 'arg' --" +# -l arg:: (optional) +test_getopt "-o a -l one:: -- -a --one" " -a --one '' --" +test_getopt "-o a -l one:: -- -a --one arg" " -a --one '' -- 'arg'" +test_getopt "-o a -l one:: -- -a --one=arg" " -a --one 'arg' --" + +# "Alternative" long options (-like -this but also --like --this). +test_getopt "-o a -a -l long -- -long --long a" " --long --long -- 'a'" + +# -u lets you avoid quoting even with modern -o. +test_getopt "-u -o a: -- -a b c" " -a b -- c" + +# Do we quote quotes right? +test_getopt "-o a -- \"it\'s\"" " -- 'it\'\''s'" +test_getopt "-o a -u -- \"it\'s\"" " -- it\'s" + +# Odds and ends. +testcmd "-T" "-T ; echo \$?" "4\n" "" "" +testcmd "-n" "-n unlikely a -x 2>&1 | grep -o unlikely:" "unlikely:\n" "" "" diff --git a/toys/pending/getopt.c b/toys/pending/getopt.c new file mode 100644 index 00000000..7780cb6e --- /dev/null +++ b/toys/pending/getopt.c @@ -0,0 +1,104 @@ +/* getopt.c - Parse command-line options + * + * Copyright 2019 The Android Open Source Project + +USE_GETOPT(NEWTOY(getopt, "^(alternative)a(name)n:(options)o:(long)(longoptions)l*Tu", TOYFLAG_USR|TOYFLAG_BIN)) + +config GETOPT + bool "getopt" + default y + help + usage: getopt [OPTIONS] [--] ARG... + + Parse command-line options for use in shell scripts. + + -a Allow long options starting with a single -. + -l OPTS Specify long options. + -n NAME Command name for error messages. + -o OPTS Specify short options. + -T Test whether this is a modern getopt. + -u Output options unquoted. +*/ + +#define FOR_getopt +#include "toys.h" + +GLOBALS( + struct arg_list *l; + char *o, *n; +) + +static void out(char *s) +{ + if (FLAG(u)) printf(" %s", s); + else { + printf(" '"); + for (; *s; s++) { + if (*s == '\'') printf("'\\''"); + else putchar(*s); + } + printf("'"); + } +} + +static char *parse_long_opt(void *data, char *str, int len) +{ + struct option **lopt_ptr = data, *lopt = *lopt_ptr; + + // Trailing : or :: means this option takes a required or optional argument. + // no_argument = 0, required_argument = 1, optional_argument = 2. + for (lopt->has_arg = 0; len>0 && str[len-1] == ':'; lopt->has_arg++) len--; + if (!len || lopt->has_arg>2) return str; + + lopt->name = xstrndup(str, len); + + (*lopt_ptr)++; + return 0; +} + +void getopt_main(void) +{ + int argc = toys.optc+1; + char **argv = xzalloc(sizeof(char *)*(argc+1)); + struct option *lopts = xzalloc(sizeof(struct option)*argc), *lopt = lopts; + int i = 0, j = 0, ch; + + if (FLAG(T)) { + toys.exitval = 4; + return; + } + + comma_args(TT.l, &lopt, "bad -l", parse_long_opt); + argv[j++] = TT.n ? TT.n : "getopt"; + + // Legacy mode: don't quote output and take the first argument as OPTSTR. + if (!FLAG(o)) { + toys.optflags |= FLAG_u; + TT.o = toys.optargs[i++]; + if (!TT.o) error_exit("no OPTSTR"); + --argc; + } + + while (i<toys.optc) argv[j++] = toys.optargs[i++]; + + // BSD getopts don't honor argv[0] (for -n), so handle errors ourselves. + opterr = 0; + optind = 1; + while ((ch = (FLAG(a)?getopt_long_only:getopt_long)(argc, argv, TT.o, + lopts, &i)) != -1) { + if (ch == '?') { + fprintf(stderr, "%s: invalid option '%c'\n", argv[0], optopt); + toys.exitval = 1; + } else if (!ch) { + printf(" --%s", lopts[i].name); + if (lopts[i].has_arg) out(optarg ? optarg : ""); + } else { + printf(" -%c", ch); + if (optarg) out(optarg); + } + } + + printf(" --"); + for (; optind<argc; optind++) out(argv[optind]); + printf("\n"); +} |