summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--getopts_long.sh701
-rwxr-xr-xlists.sh66
2 files changed, 767 insertions, 0 deletions
diff --git a/getopts_long.sh b/getopts_long.sh
new file mode 100644
index 0000000..ce2f7f0
--- /dev/null
+++ b/getopts_long.sh
@@ -0,0 +1,701 @@
1#! /bin/echo Usage:.
2#
3# getopts_long -- POSIX shell getopts with GNU-style long option support
4#
5# Copyright 2005-2009 Stephane Chazelas <stephane_chazelas@yahoo.fr>
6#
7# Permission to use, copy, modify, distribute, and sell this software and
8# its documentation for any purpose is hereby granted without fee, provided
9# that the above copyright notice appear in all copies and that both that
10# copyright notice and this permission notice appear in supporting
11# documentation. No representations are made about the suitability of this
12# software for any purpose. It is provided "as is" without express or
13# implied warranty.
14
15# History:
16# 2005 ?? - 1.0
17# first version
18# 2009-08-12 - 1.1
19# thanks to ujqm8360@netwtc.net for helping fix a few bugs:
20# - -ab x where -b accepts arguments wasn't handled properly. Also,
21# $OPTLIND wasn't set properly (at least not the same way as most
22# getopts implementation do).
23# - The handling of ambiguous long options was incorrect.
24
25getopts_long() {
26 # args: shortopts, var, [name, type]*, "", "$@"
27 #
28 # getopts_long parses command line arguments. It works like the
29 # getopts shell built-in command except that it also recognises long
30 # options a la GNU.
31 #
32 # You must provide getopts_long with the list of supported single
33 # letter options in the same format as getopts', followed by the
34 # variable name you want getopts_long to return the current processed
35 # option in, followed by the list of long option names (without the
36 # leading "--") and types (0 or no_argument, 1 or required_argument,
37 # 2 or optional_argument). The end of the long option specification
38 # is indicated by an empty argument. Then follows the list of
39 # arguments to be parsed.
40 #
41 # The $OPTLIND variable must be set to 1 before the first call of the
42 # getopts_long function to process a given list of arguments.
43 #
44 # getopts_long returns the value of the current option in the variable
45 # whose name is provided as its second argument (be careful to avoid
46 # variables that have a special signification to getopts_long or the
47 # shell or any other tool you may call from your script). If the
48 # current option is a single letter option, then it is returned
49 # without the leading "-". If it's a long option (possibly
50 # abbreviated), then the full name of the option (without the leading
51 # "--") is returned. If the option has an argument, then it is stored
52 # in the $OPTLARG variable. If the current option is not recognised,
53 # or if it is provided with an argument while it is not expecting one
54 # (as in --opt=value) or if it is not provided with an argument while
55 # it is expecting one, or if the option is so abbreviated that it is
56 # impossible to identify the option uniquely, then:
57 # - if the short option specifications begin with ":", getopts_long
58 # returns ":" in the output variable and $OPTLARG contains the
59 # faulty option name (in full except in the case of the ambiguous
60 # or bad option) and $OPTLERR contains the error message.
61 # - if not, then getopts_long behaves the same as above except that
62 # it returns "?" instead of ":", leaves $OPTLARG unset and
63 # displays the error message on stderr.
64 #
65 # The exit status of getopts_long is 0 unless the end of options is
66 # reached or an error is encountered in the syntax of the getopts_long
67 # call.
68 #
69 # After getopts_long has finished processing the options, $OPTLIND
70 # contains the index of the first non-option argument or $# + 1 if
71 # there's no non-option argument.
72 #
73 # The "=" character is not allowed in a long option name. Any other
74 # character is. "-" and ":" are not allowed as short option names. Any
75 # other character is. If a short option appears more than once in the
76 # specification, the one with the greatest number of ":"s following it
77 # is retained. If a long option name is provided more than once, only
78 # the first one is taken into account. Note that if you have both a -a
79 # and --a option, there's no way to differentiate them. Beside the
80 # $OPTLIND, $OPTLARG, and $OPTLERR, getopts_long uses the $OPTLPENDING
81 # variable to hold the remaining options to be processed for arguments
82 # with several one-letter options. That variable shouldn't be used
83 # anywhere else in your script. Those 4 variables are the only ones
84 # getopts_long may modify.
85 #
86 # Dependency: only POSIX utilities are called by that function. They
87 # are "set", "unset", "shift", "break", "return", "eval", "command",
88 # ":", "printf" and "[". Those are generally built in the POSIX
89 # shells. Only "printf" has been known not to be in some old versions
90 # of bash, zsh or ash based shells.
91 #
92 # Differences with the POSIX getopts:
93 # - if an error is detected during the parsing of command line
94 # arguments, the error message is stored in the $OPTLERR variable
95 # and if the first character of optstring is ':', ':' is returned in
96 # any case.
97 # - in the single-letter option specification, if a letter is
98 # followed by 2 colons ("::"), then the option can have an optional
99 # argument as in GNU getopt(3). In that case, the argument must
100 # directly follow the option as in -oarg (not -o arg).
101 # - there must be an empty argument to mark the end of the option
102 # specification.
103 # - long options starting with "--" are supported.
104 #
105 # Differences with GNU getopt_long(3):
106 # - getopts_long doesn't allow options to be interspersed with other
107 # arguments (as if POSIXLY_CORRECT was set for GNU getopt_long(3))
108 # - there's no linkage of any sort between the short and long
109 # options. The caller is responsible of that (see example below).
110 #
111 # Compatibility:
112 # getopts_long code is (hopefully) POSIX.2/SUSv3 compliant. It won't
113 # work with the Bourne/SystemV shell. Use /usr/xpg4/bin/sh or ksh or
114 # bash on Solaris.
115 # It has been tested successfully with:
116 # - bash 3.0 (patch level 16) on Cygwin
117 # - zsh 4.2.4 on Solaris 2.7
118 # - /usr/xpg4/bin/sh (same as /usr/bin/ksh) (ksh88i) on Solaris 2.7
119 # - /usr/dt/bin/dtksh (ksh93d) on Solaris 2.7
120 # - /usr/bin/ksh (pdksh 5.2.14) on Linux
121 # - zsh 3.0.6 on Solaris 2.8
122 # - bash 2.0.3 on Solaris 2.8
123 # - dash 0.5.2 on Linux
124 # - bash 2.05b (patch level 0) on Linux
125 # - ksh93p and ksh93q on Linux (ksh93t+ crashes)
126 #
127 # It is known to fail with those non-POSIX compliant shells:
128 # - /bin/sh on Solaris
129 # - /usr/bin/sh on Cygwin
130 # - bash 1.x
131 #
132 # Bugs:
133 # please report them to <stephane_chazelas@yahoo.fr>
134 #
135 # Example:
136 #
137 # verbose=false opt_bar=false bar=default_bar foo=default_foo
138 # opt_s=false opt_long=false
139 # OPTLIND=1
140 # while getopts_long :sf:b::vh opt \
141 # long 0 \
142 # foo required_argument \
143 # bar 2 \
144 # verbose no_argument \
145 # help 0 "" "$@"
146 # do
147 # case "$opt" in
148 # s) opt_s=true;;
149 # long) opt_long=true;;
150 # v|verbose) verbose=true;;
151 # h|help) usage; exit 0;;
152 # f|foo) foo=$OPTLARG;;
153 # b|bar) bar=${OPTLARG-$bar};;
154 # :) printf >&2 '%s: %s\n' "${0##*/}" "$OPTLERR"
155 # usage
156 # exit 1;;
157 # esac
158 # done
159 # shift "$(($OPTLIND - 1))"
160 # # process the remaining arguments
161
162 [ -n "${ZSH_VERSION+z}" ] && emulate -L sh
163
164 unset OPTLERR OPTLARG || :
165
166 case "$OPTLIND" in
167 "" | 0 | *[!0-9]*)
168 # First time in the loop. Initialise the parameters.
169 OPTLIND=1
170 OPTLPENDING=
171 ;;
172 esac
173
174 if [ "$#" -lt 2 ]; then
175 printf >&2 'getopts_long: not enough arguments\n'
176 return 1
177 fi
178
179 # validate variable name. Need to fix locale for character ranges.
180 LC_ALL=C command eval '
181 case "$2" in
182 *[!a-zA-Z_0-9]*|""|[0-9]*)
183 printf >&2 "getopts_long: invalid variable name: \`%s'\''\n" "$2"
184 return 1
185 ;;
186 esac'
187
188 # validate short option specification
189 case "$1" in
190 ::*|*:::*|*-*)
191 printf >&2 "getopts_long: invalid option specification: \`%s'\n" "$1"
192 return 1
193 ;;
194 esac
195
196 # validate long option specifications
197
198 # POSIX shells only have $1, $2... as local variables, hence the
199 # extensive use of "set" in that function.
200
201 set 4 "$@"
202 while :; do
203 if
204 [ "$1" -gt "$#" ] || {
205 eval 'set -- "${'"$1"'}" "$@"'
206 [ -n "$1" ] || break
207 [ "$(($2 + 2))" -gt "$#" ]
208 }
209 then
210 printf >&2 "getopts_long: long option specifications must end in an empty argument\n"
211 return 1
212 fi
213 eval 'set -- "${'"$(($2 + 2))"'}" "$@"'
214 # $1 = type, $2 = name, $3 = $@
215 case "$2" in
216 *=*)
217 printf >&2 "getopts_long: invalid long option name: \`%s'\n" "$2"
218 return 1
219 ;;
220 esac
221 case "$1" in
222 0 | no_argument) ;;
223 1 | required_argument) ;;
224 2 | optional_argument) ;;
225 *)
226 printf >&2 "getopts_long: invalid long option type: \`%s'\n" "$1"
227 return 1
228 ;;
229 esac
230 eval "shift 3; set $(($3 + 2))"' "$@"'
231 done
232 shift
233
234 eval "shift; set $(($1 + $OPTLIND))"' "$@"'
235
236 # unless there are pending short options to be processed (in
237 # $OPTLPENDING), the current option is now in ${$1}
238
239 if [ -z "$OPTLPENDING" ]; then
240 [ "$1" -le "$#" ] || return 1
241 eval 'set -- "${'"$1"'}" "$@"'
242
243 case "$1" in
244 --)
245 OPTLIND=$(($OPTLIND + 1))
246 return 1
247 ;;
248 --*)
249 OPTLIND=$(($OPTLIND + 1))
250 ;;
251 -?*)
252 OPTLPENDING="${1#-}"
253 shift
254 ;;
255 *)
256 return 1
257 ;;
258 esac
259 fi
260
261 if [ -n "$OPTLPENDING" ]; then
262 # WA for zsh and bash 2.03 bugs:
263 OPTLARG=${OPTLPENDING%"${OPTLPENDING#?}"}
264 set -- "$OPTLARG" "$@"
265 OPTLPENDING="${OPTLPENDING#?}"
266 unset OPTLARG
267
268 # $1 = current option = ${$2+1}, $3 = $@
269
270 [ -n "$OPTLPENDING" ] ||
271 OPTLIND=$(($OPTLIND + 1))
272
273 case "$1" in
274 [-:])
275 OPTLERR="bad option: \`-$1'"
276 case "$3" in
277 :*)
278 eval "$4=:"
279 OPTLARG="$1"
280 ;;
281 *)
282 printf >&2 '%s\n' "$OPTLERR"
283 eval "$4='?'"
284 ;;
285 esac
286 ;;
287
288 *)
289 case "$3" in
290 *"$1"::*) # optional argument
291 eval "$4=\"\$1\""
292 if [ -n "$OPTLPENDING" ]; then
293 # take the argument from $OPTLPENDING if any
294 OPTLARG="$OPTLPENDING"
295 OPTLPENDING=
296 OPTLIND=$(($OPTLIND + 1))
297 fi
298 ;;
299
300 *"$1":*) # required argument
301 if [ -n "$OPTLPENDING" ]; then
302 # take the argument from $OPTLPENDING if any
303 OPTLARG="$OPTLPENDING"
304 eval "$4=\"\$1\""
305 OPTLPENDING=
306 OPTLIND=$(($OPTLIND + 1))
307 else
308 # take the argument from the next argument
309 if [ "$(($2 + 2))" -gt "$#" ]; then
310 OPTLERR="option \`-$1' requires an argument"
311 case "$3" in
312 :*)
313 eval "$4=:"
314 OPTLARG="$1"
315 ;;
316 *)
317 printf >&2 '%s\n' "$OPTLERR"
318 eval "$4='?'"
319 ;;
320 esac
321 else
322 OPTLIND=$(($OPTLIND + 1))
323 eval "OPTLARG=\"\${$(($2 + 2))}\""
324 eval "$4=\"\$1\""
325 fi
326 fi
327 ;;
328
329 *"$1"*) # no argument
330 eval "$4=\"\$1\""
331 ;;
332 *)
333 OPTLERR="bad option: \`-$1'"
334 case "$3" in
335 :*)
336 eval "$4=:"
337 OPTLARG="$1"
338 ;;
339 *)
340 printf >&2 '%s\n' "$OPTLERR"
341 eval "$4='?'"
342 ;;
343 esac
344 ;;
345 esac
346 ;;
347 esac
348 else # long option
349
350 # remove the leading "--"
351 OPTLPENDING="$1"
352 shift
353 set 6 "${OPTLPENDING#--}" "$@"
354 OPTLPENDING=
355
356 while
357 eval 'set -- "${'"$1"'}" "$@"'
358 [ -n "$1" ]
359 do
360 # $1 = option name = ${$2+1}, $3 => given option = ${$4+3}, $5 = $@
361
362 case "${3%%=*}" in
363 "$1")
364 OPTLPENDING=EXACT
365 break;;
366 esac
367
368 # try to see if the current option can be seen as an abbreviation.
369 case "$1" in
370 "${3%%=*}"*)
371 if [ -n "$OPTLPENDING" ]; then
372 [ "$OPTLPENDING" = AMBIGUOUS ] || eval '[ "${'"$(($OPTLPENDING + 1))"'}" = "$1" ]' ||
373 OPTLPENDING=AMBIGUOUS
374 # there was another different option matching the current
375 # option. The eval thing is in case one option is provided
376 # twice in the specifications which is OK as per the
377 # documentation above
378 else
379 OPTLPENDING="$2"
380 fi
381 ;;
382 esac
383 eval "shift 2; set $(($2 + 2)) "'"$@"'
384 done
385
386 case "$OPTLPENDING" in
387 AMBIGUOUS)
388 OPTLERR="option \`--${3%%=*}' is ambiguous"
389 case "$5" in
390 :*)
391 eval "$6=:"
392 OPTLARG="${3%%=*}"
393 ;;
394 *)
395 printf >&2 '%s\n' "$OPTLERR"
396 eval "$6='?'"
397 ;;
398 esac
399 OPTLPENDING=
400 return 0
401 ;;
402 EXACT)
403 eval 'set "${'"$(($2 + 2))"'}" "$@"'
404 ;;
405 "")
406 OPTLERR="bad option: \`--${3%%=*}'"
407 case "$5" in
408 :*)
409 eval "$6=:"
410 OPTLARG="${3%%=*}"
411 ;;
412 *)
413 printf >&2 '%s\n' "$OPTLERR"
414 eval "$6='?'"
415 ;;
416 esac
417 OPTLPENDING=
418 return 0
419 ;;
420 *)
421 # we've got an abbreviated long option.
422 shift
423 eval 'set "${'"$(($OPTLPENDING + 1))"'}" "${'"$OPTLPENDING"'}" "$@"'
424 ;;
425 esac
426
427 OPTLPENDING=
428
429 # $1 = option type, $2 = option name, $3 unused,
430 # $4 = given option = ${$5+4}, $6 = $@
431
432 case "$4" in
433 *=*)
434 case "$1" in
435 1 | required_argument | 2 | optional_argument)
436 eval "$7=\"\$2\""
437 OPTLARG="${4#*=}"
438 ;;
439 *)
440 OPTLERR="option \`--$2' doesn't allow an argument"
441 case "$6" in
442 :*)
443 eval "$7=:"
444 OPTLARG="$2"
445 ;;
446 *)
447 printf >&2 '%s\n' "$OPTLERR"
448 eval "$7='?'"
449 ;;
450 esac
451 ;;
452 esac
453 ;;
454
455 *)
456 case "$1" in
457 1 | required_argument)
458 if [ "$(($5 + 5))" -gt "$#" ]; then
459 OPTLERR="option \`--$2' requires an argument"
460 case "$6" in
461 :*)
462 eval "$7=:"
463 OPTLARG="$2"
464 ;;
465 *)
466 printf >&2 '%s\n' "$OPTLERR"
467 eval "$7='?'"
468 ;;
469 esac
470 else
471 OPTLIND=$(($OPTLIND + 1))
472 eval "OPTLARG=\"\${$(($5 + 5))}\""
473 eval "$7=\"\$2\""
474 fi
475 ;;
476 *)
477 # optional argument (but obviously not provided) or no
478 # argument
479 eval "$7=\"\$2\""
480 ;;
481 esac
482 ;;
483 esac
484 fi
485 return 0
486}
487
488# testing code
489if [ -n "$test_getopts_long" ]; then
490test_getopts_long() {
491 expected="$1" had=
492 shift
493 OPTLIND=1
494
495 while err="$(set +x;getopts_long "$@" 2>&1 > /dev/null)"
496 getopts_long "$@" 2> /dev/null; do
497 eval "opt=\"\$$2\""
498 had="$had|$opt@${OPTLARG-unset}@${OPTLIND-unset}@${OPTLERR-unset}@$err"
499 done
500 had="$had|${OPTLIND-unset}|$err"
501
502 if [ "$had" = "$expected" ]; then
503 echo PASS
504 else
505 echo FAIL
506 printf 'Expected: %s\n Got: %s\n' "$expected" "$had"
507 fi
508}
509while IFS= read -r c && IFS= read -r e; do
510 printf '+ %-72s ' "$c"
511 #set -x
512 eval "test_getopts_long \"\$e\" $c"
513done << \EOF
514: a
515|1|getopts_long: long option specifications must end in an empty argument
516:a opt "" -a
517|a@unset@2@unset@|2|
518:a opt "" -a b
519|a@unset@2@unset@|2|
520:a opt "" -a -a
521|a@unset@2@unset@|a@unset@3@unset@|3|
522:a opt "" -ab
523|a@unset@1@unset@|:@b@2@bad option: `-b'@|2|
524:a: opt "" -ab
525|a@b@2@unset@|2|
526:a: opt "" -a b
527|a@b@3@unset@|3|
528:a: opt "" -a -a
529|a@-a@3@unset@|3|
530:a: opt "" -a
531|:@a@2@option `-a' requires an argument@|2|
532:a:: opt "" -a
533|a@unset@2@unset@|2|
534:a:: opt "" -ab
535|a@b@2@unset@|2|
536:a:: opt "" -a b
537|a@unset@2@unset@|2|
538:ab: opt "" -ab c
539|a@unset@1@unset@|b@c@3@unset@|3|
540:a:: opt "" -a -a
541|a@unset@2@unset@|a@unset@3@unset@|3|
542:a:: opt "" -:a:
543|:@:@1@bad option: `-:'@|a@:@2@unset@|2|
544:= opt ""
545|1|
546:: opt ""
547|1|getopts_long: invalid option specification: `::'
548: opt ""
549|1|
550:a:a opt "" -a
551|:@a@2@option `-a' requires an argument@|2|
552:a::a opt "" -a
553|a@unset@2@unset@|2|
554:ab:c:: opt "" -abc -cba -bac
555|a@unset@1@unset@|b@c@2@unset@|c@ba@3@unset@|b@ac@4@unset@|4|
556: opt abc 0 "" --abc
557|abc@unset@2@unset@|2|
558: opt abc no_argument "" --abc
559|abc@unset@2@unset@|2|
560: opt abc no_argument "" --abc=foo
561|:@abc@2@option `--abc' doesn't allow an argument@|2|
562: opt abc no_argument "" --abc foo
563|abc@unset@2@unset@|2|
564: opt abc 1 "" --abc=foo
565|abc@foo@2@unset@|2|
566: opt abc required_argument "" --abc foo
567|abc@foo@3@unset@|3|
568: opt abc required_argument "" --abc=
569|abc@@2@unset@|2|
570: opt abc required_argument "" --abc
571|:@abc@2@option `--abc' requires an argument@|2|
572: opt abc 2 "" --abc
573|abc@unset@2@unset@|2|
574: opt abc optional_argument "" --abc=
575|abc@@2@unset@|2|
576: opt abc optional_argument "" --abc=foo
577|abc@foo@2@unset@|2|
578: opt abc optional_argument "" --abc --abc
579|abc@unset@2@unset@|abc@unset@3@unset@|3|
580: opt abc 0 abcd 0 "" --abc
581|abc@unset@2@unset@|2|
582: opt abc 0 abd 0 "" --ab
583|:@ab@2@option `--ab' is ambiguous@|2|
584: opt abc 0 abcd 0 "" --ab
585|:@ab@2@option `--ab' is ambiguous@|2|
586: opt abc 0 abc 1 "" --ab
587|abc@unset@2@unset@|2|
588: opt abc 0 abc 1 "" --abc
589|abc@unset@2@unset@|2|
590: opt abc 0 abc 1 "" --ab
591|abc@unset@2@unset@|2|
592: opt abc 0 acd 0 "" --ab
593|abc@unset@2@unset@|2|
594:abc:d:e::f:: opt ab 0 ac 1 bc 2 cd 1 cde 2 "" -abcdef -a -f -c --a --a= --b=foo -fg
595|a@unset@1@unset@|b@unset@1@unset@|c@def@2@unset@|a@unset@3@unset@|f@unset@4@unset@|c@--a@6@unset@|:@a@7@option `--a' is ambiguous@|bc@foo@8@unset@|f@g@9@unset@|9|
596a opt "" -a
597|a@unset@2@unset@|2|
598a opt "" -a b
599|a@unset@2@unset@|2|
600a opt "" -a -a
601|a@unset@2@unset@|a@unset@3@unset@|3|
602a opt "" -ab
603|a@unset@1@unset@|?@unset@2@bad option: `-b'@bad option: `-b'|2|
604a: opt "" -ab
605|a@b@2@unset@|2|
606a: opt "" -a b
607|a@b@3@unset@|3|
608a: opt "" -a -a
609|a@-a@3@unset@|3|
610a: opt "" -a
611|?@unset@2@option `-a' requires an argument@option `-a' requires an argument|2|
612a:: opt "" -a
613|a@unset@2@unset@|2|
614a:: opt "" -ab
615|a@b@2@unset@|2|
616a:: opt "" -a b
617|a@unset@2@unset@|2|
618a:: opt "" -a -a
619|a@unset@2@unset@|a@unset@3@unset@|3|
620a:: opt "" -:a:
621|?@unset@1@bad option: `-:'@bad option: `-:'|a@:@2@unset@|2|
622= opt ""
623|1|
624: opt ""
625|1|
626'' opt ""
627|1|
628a:a opt "" -a
629|?@unset@2@option `-a' requires an argument@option `-a' requires an argument|2|
630a::a opt "" -a
631|a@unset@2@unset@|2|
632ab:c:: opt "" -abc -cba -bac
633|a@unset@1@unset@|b@c@2@unset@|c@ba@3@unset@|b@ac@4@unset@|4|
634'' opt abc 0 "" --abc
635|abc@unset@2@unset@|2|
636'' opt abc no_argument "" --abc
637|abc@unset@2@unset@|2|
638'' opt abc no_argument "" --abc=foo
639|?@unset@2@option `--abc' doesn't allow an argument@option `--abc' doesn't allow an argument|2|
640'' opt abc no_argument "" --abc foo
641|abc@unset@2@unset@|2|
642'' opt abc 1 "" --abc=foo
643|abc@foo@2@unset@|2|
644'' opt abc required_argument "" --abc foo
645|abc@foo@3@unset@|3|
646'' opt abc required_argument "" --abc=
647|abc@@2@unset@|2|
648'' opt abc required_argument "" --abc
649|?@unset@2@option `--abc' requires an argument@option `--abc' requires an argument|2|
650'' opt abc 2 "" --abc
651|abc@unset@2@unset@|2|
652'' opt abc optional_argument "" --abc=
653|abc@@2@unset@|2|
654'' opt abc optional_argument "" --abc=foo
655|abc@foo@2@unset@|2|
656'' opt abc optional_argument "" --abc --abc
657|abc@unset@2@unset@|abc@unset@3@unset@|3|
658'' opt abc 0 abcd 0 "" --abc
659|abc@unset@2@unset@|2|
660'' opt abc 0 abd 0 "" --ab
661|?@unset@2@option `--ab' is ambiguous@option `--ab' is ambiguous|2|
662'' opt abc 0 abcd 0 "" --ab
663|?@unset@2@option `--ab' is ambiguous@option `--ab' is ambiguous|2|
664'' opt abc 0 abc 1 "" --ab
665|abc@unset@2@unset@|2|
666'' opt abc 0 abc 1 "" --abc
667|abc@unset@2@unset@|2|
668'' opt abc 0 abc 1 "" --ab
669|abc@unset@2@unset@|2|
670'' opt abc 0 acd 0 "" --ab
671|abc@unset@2@unset@|2|
672abc:d:e::f:: opt ab 0 ac 1 bc 2 cd 1 cde 2 "" -abcdef -a -f -c --a --a= --b=foo -fg
673|a@unset@1@unset@|b@unset@1@unset@|c@def@2@unset@|a@unset@3@unset@|f@unset@4@unset@|c@--a@6@unset@|?@unset@7@option `--a' is ambiguous@option `--a' is ambiguous|bc@foo@8@unset@|f@g@9@unset@|9|
674: '' '' a
675|1|getopts_long: invalid variable name: `'
676: 1a ''
677|1|getopts_long: invalid variable name: `1a'
678- a
679|1|getopts_long: invalid option specification: `-'
680:a::a:abcd o ab 1 abc 1 abd 1 abe 1 abc 2 '' -aa --ab 1 --abc
681|a@a@2@unset@|ab@1@4@unset@|:@abc@5@option `--abc' requires an argument@|5|
682:
683|1|getopts_long: not enough arguments
684'\[$' o -- 0 ' ' 1 '#' required_argument '' '-\\\[$' --\ =a --\#=\$\$
685|\@unset@1@unset@|\@unset@1@unset@|\@unset@1@unset@|[@unset@1@unset@|$@unset@2@unset@| @a@3@unset@|#@$$@4@unset@|4|
686: o a 1 b 2 c
687|1|getopts_long: long option specifications must end in an empty argument
688: o a 1 b 2
689|1|getopts_long: long option specifications must end in an empty argument
690: o a 1 b 2 c 3 '' --c
691|1|getopts_long: invalid long option type: `3'
692": " o " " 1 '' "- " "-- =1"
693| @unset@1@unset@| @unset@2@unset@| @1@3@unset@|3|
694: o a 1 '' --c
695|:@c@2@bad option: `--c'@|2|
696: o a 1 '' --c=foo
697|:@c@2@bad option: `--c'@|2|
698: o ab 1 ac 1 ad 1 a 1 '' --a=1
699|a@1@2@unset@|2|
700EOF
701fi
diff --git a/lists.sh b/lists.sh
new file mode 100755
index 0000000..c07870a
--- /dev/null
+++ b/lists.sh
@@ -0,0 +1,66 @@
1#!/usr/bin/env bash
2
3shopt -s extglob nullglob
4
5. ./getopts_long.sh
6
7find_lists() {
8 LISTS=()
9 while read -r -d $'\0'; do
10 local list=$(realpath --relative-to=. "$REPLY")
11 LISTS+=("$list")
12 done < <(find lists -maxdepth 1 -mindepth 1 -type d -not -name '.*' -print0 | sort -z)
13}
14
15find_posts() {
16 local list="$1"
17 shift 1
18
19 POSTS=()
20 for p in "$list"/!(*[!0-9]*); do
21 POSTS+=( "$p" )
22 done
23}
24
25print_lists() {
26 find_lists
27 for x in "${LISTS[@]}"; do
28 printf "%s: %s\n" "$x" "$(<$x/title)"
29 done
30}
31
32print_posts() {
33 find_posts "$1"
34 for p in "${POSTS[@]}"; do
35 local post=$(readlink "$p")
36 printf "%s: %s\n" "${p##*/}" "${post##*/}"
37 done
38}
39
40add_list() {
41 local list="$1"
42 mkdir lists/"$list"
43 shift 1
44 echo "$@" >lists/"$list"/title
45}
46
47while getopts_long ":la:" opt \
48 posts required_argument \
49 "" "$@"
50do
51 case $opt in
52 l)
53 print_lists
54 exit 0;;
55 a)
56 shift "$(($OPTLIND - 1))"
57 add_list "$OPTLARG" "$@"
58 exit 0;;
59 posts)
60 print_posts "$OPTLARG"
61 ;;
62 :)
63 printf >&2 '%s: %s\n' "${0##*/}" "$OPTLERR"
64 exit 1;;
65 esac
66done