aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2019-11-21 14:09:23 -0800
committerRob Landley <rob@landley.net>2019-11-22 06:54:31 -0600
commitc77b66455762f42bb824c1aa8cc60e7f4d44bdab (patch)
tree72a44d951b912d43290ef0ee6bc705c2da0f2876
parent176c6fa4580571d262434ca2d610c860d30cf876 (diff)
downloadtoybox-c77b66455762f42bb824c1aa8cc60e7f4d44bdab.tar.gz
Add getopt(1).
Includes new tests.
-rw-r--r--lib/portability.h3
-rwxr-xr-xtests/getopt.test44
-rw-r--r--toys/pending/getopt.c104
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");
+}