#!/bin/sh # platform ... devuan dash # GPL_3+ cat << 'EEE' > /dev/null /* ckopt ... option parse helper. bourne-shell script. * Copyright (C) 2018 Momi-g * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ EEE # --- recommend info # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02 # https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr12/index.html # https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr1n/index.html # --- refs. # https://www.mm2d.net/main/prog/c/getopt-03.html # https://wp.mikeforce.net/gnome/category/gtk # https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/html/gtk-General.html#gtk-init-with-args # https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/glib/glib-Commandline-option-parser.html#GOptionEntry # http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html # make namespace lname=${0##*/} optpre="opt_" pbuf="${lname}_buf" popt="${lname}_opt" pfunc="$lname""_func" if [ "${1:-null}" = "-h" ] || [ $# -ne 1 ] ; then cat << 'EEE' HowTo (ckopt, option parser & checker. bourne-shell script) opt: -h, -H(detail help) ------ eg.) ...save as 'yoursh.sh' #!/bin/sh cmd=$(cat << 'END' # notice quote. 'END'.. not expand $foo etc. # opt dfl type ( +add command you want to test input opt) -a 100 inT -b vvv STr '[ "$opt_b" != "bob" ]' -c "0" 'bool' 'echo "detect c" >/dev/stderr' '#E myflg="$opt_c"' END ) buf=`ckopt ":$cmd"` #..or.. "$cmd" (colon, quiet err mode like 'getopts' ) eval "$buf" if [ "$?" = "1" ] ; then echo "err. $OPTARG" ; exit 1 ; fi echo "$opt_a $opt_b $myflg" # >>> check working. opt_* is fixed prefix. echo "$@" ... run 'yoursh.sh zzz -b ttt yyy -- -a 10' >>> 100 ttt 0 (a b myflg) + show msg if input -c zzz yyy -a 10 >>> parse until detect '--' or end. remove options. eg.) ~$ ckopt "$cmd" # >>> output code. copy&paste to your script directly. EEE exit 0 fi if [ "${1:-null}" = "-H" ] ; then cat << 'EEE' *** samples are written at the end *** --- check sequence samle ini: '-a' 111 int "cmd1" "cmd2" cmd: aaa.sh -a 123 (or -a zzz etc) >>> opt_a="111" (always run) -a get? -y-> int? -y-> opt_a=123 -> cmd1 -suc-> cmd2 -suc-> ... +-n-> (opt_a=111) +-n-> rtn err +err-> rtn err --- optlist format: # ignore line head '#(+space/tab)'. (-v "bool" 0 # comment ...NG) # column1 clm2 clm3 clm4,5,6... # (must) (must) (must) (optional) -v 0 "bool" -n 111 iNt '#E linenum=$opt_n' '-j' "abc" str 'test -e "$opt_j"' -b '`printf "a\nbc\n"`' STR echo\ aaa - splited by blank. (space/tab) - line parsing. you cant use newline char directly. use `printf 'a\nbc\n'` etc. - grammar conforms to shell style. quote, backslash, grouping etc. - clm1: option char (posix) short option only. allows a-z,A-Z,0-9. (-@, -_ ... NG). - clm2: defalut value the dfl value skips int/bool/str test. (allow '-v' 'bob' 'bool') - clm3: option type you expect. 3 types. bool: "0" or "1". switch type option.(-v, -h etc.) int: integer. 9, 011(octal), 0x09(hex) etc. converted to decimal.(opt_n=9) checked by ' printf "%d" "$opt_n" ' suc or not. 'printf' + 012/0x11/123 (shell command) is posix. portable. str: others. use as string. (hello -> opt_j='hello', 0x11 -> opt_j='0x11' ) - clm4,5,6... : additional command not must. write commands you want to execute. -a 0 bool >>> myscript -a ... opt_a=1 -a 0 bool 'echo "abcde"' >>> myscript -a ... opt_a=1 and disp 'abcde' stop parsing if command returns $?=1 (exit status = 1) -a 0 bool '[ "1" = "0" ]' "echo 'aaa'" >>> myscript -a ... opt_a=1 only (skip echo) cmd with prefix '#E' runs at script end. the following descriptions are almost equivalent. this feature is intended to summarize the option handling. aaa=`ckopt "-a int 0"` aaa=`ckopt "-a int 0 '#E flg=$opt_a'"` eval "$aaa" <-> eval "$aaa" flg=$opt_a (delete) -a int 100 '#E zzz=$opt_a' <-> -a int 100 -b bool 1 -b bool 1 '#E zzz=$opt_a' --- err handling: if check returns err, disp msg & stop process if no colon is set.("$optlist") if colon is set.(":$optlist"), run as quiet err mode. exit status returns 1 and set errmsg (errmsg -a 'cmd' etc) to 'OPTARG'. (uses OPTARG as msgbuf) -stop mode aaa=`ckopt "$optlist"` eval "$aaa" # errmsg & stop -quiet mode aaa=`ckopt ":$optlist"` eval "$aaa" if [ "$?" = "1" ] ;then echo "mmm... err !! $OPTARG" # >>>errmsg will be printed. exit 1 fi ... --- copy & paste (portable use + low overhead): if you execute directly, code will be printed. ~$ optlist='-h "bool" 0' ~$ ckopt "$optlist" >>> output code you can use this code directly. #!/bin/sh buf=' ---> --- # help ---> -h "bool" 0 ---> copy & paste output code ' ---> (100 - 150 lines) ---> aaa=`ckopt ":$buf"` ---> eval "$aaa" ---> --- if [ "$?" = "1" ] ;then if [ "$?" = "1" ] ;then time: 40-100 ms (normal use) 4-8 ms (copy & paste style) EEE cat << EEE0 ; cat << 'EEE' using vars: $pbuf $popt $pfunc OPTARG OPTIND opt_a,b,c... EEE0 -------------------------- ******** samples ********* -------------------------- #!/bin/sh buf=' # help -h 0 "bool" ' aaa=`ckopt "$buf"` eval "$aaa" --- #!/bin/sh func_usage() { echo "hello,world" # return 0 exit 0 } buf=`cat << 'END' -h 0 "bool" func_usage END ` bbb=`ckopt ":$buf"` eval "$bbb" if [ "$?" = "1" ] ;then echo "invalid opt. $OPTARG" >/dev/stderr exit 1 fi echo "good-bye world" --- #!/bin/sh workdir=${0%/*} # $0 ... /aaa/bb/ccc.sh >> /aaa/bb uname="anon yno us" func_usage() { echo "hello,world" ; exit 0 ; } buf=`cat << 'END' -h 0 "bool" func_usage # ---debag level (1 - 10) -d 1 int '[ 1 -le $opt_d ] && [ $opt_d -le 10 ]' 'echo "dlevel=$opt_d"' # ---uniq tmpfile name & make test # make test runs if '-f' opt detected -f 'mytmp' str '[ ! -e "$opt_f" ]' 'touch "$opt_f"' 'rm "$opt_f"' # username. kick length=0 -u "$uname" str '[ ! -z "$opt_u" ]' '#E uname="$opt_u"' # working dir -w "$workdir" str "[ -d \"\$opt_w\" ]" workdir="$opt_w" END ` abc=`ckopt "$buf"` eval "$abc" echo "ckname=@ $uname @" # --- recommend info # http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html#tag_12_02 # https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr12/index.html # https://docs.oracle.com/cd/E19455-01/816-3518/6m9ptvr1n/index.html # --- refs. # https://www.mm2d.net/main/prog/c/getopt-03.html # https://wp.mikeforce.net/gnome/category/gtk # https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/html/gtk-General.html#gtk-init-with-args # https://documents.mikeforce.net/gtk+-2.14.x-refs/gtk/glib/glib-Commandline-option-parser.html#GOptionEntry # http://catb.org/%7Eesr/writings/taoup/html/ch10s05.html EEE exit 0 fi # posixでawkはEREのスーパーセットと決められている。 ck_posixopt() { printf '%s\n' "$*" | tr ' ' '\n' | awk ' $1 != "" { buf=match($1, /^-[a-zA-Z0-9]/) if(length($1) != 2 || buf == 0) { printf "%s ", $1 err=1 } } END{ if(err == 1) { exit 1 } }' } errstop() { printf '%s\n' "$1" while : ; do sleep 1000 ; done exit 1 } # output... #casemaker "$fmt" b '[ "$opt_b" != "bob" ]' '#E myflg="$opt_b"' #opt_a=1 #:a:bc # fnc "$rawlist" func_getfmt() { buf=`printf '%s\n' "$*" | awk ' $1 != "" && $1 !~ /^#/ {$3=tolower($3) ; print $0}' | awk '{print "func_outfmt " $0} '` eval "$buf" } func_outfmt() { buf=':' #have subargs test "${3#*bool*}" = "$3" || buf="" if [ $# -lt 3 ] ; then buf='::' #listerr fi printf '%s%s' "${1#?}" "$buf" } # 外部実行リストをまとめて処理しとく。func名と他。 # fmtがopt名順に並んでるはず。拝借できる。 # ckopt_func -d/-c -a 000 int ls ... func_execlist() { printf '%s\n' "$*" | awk ' $1 != "" && $1 !~ /^#/ {print $0}' | awk -v pfunc="$pfunc" -v fmt="$fmt" -v optpre="$optpre" -v popt="$popt" ' BEGIN{ pos=1 } { char=substr(fmt, pos, 1) # abcv ... の順番にcharを一個ずつ subarg=substr(fmt, pos+1, 1) # subck print "# " pfunc " -d " $0 # dflout #caseout printf("if [ \042$%s\042 = \042%s\042 ] ; then\n", popt, char) rhs=1 if(subarg == ":"){ pos=pos+1 rhs="$" "OPTARG" } pos=pos+1 printf(" %s%s=\042%s\042\n", optpre, char, rhs) printf(" %s -c %s\n", pfunc, $0) # errはpfuncで処理する。 printf(" continue\n") printf("fi\n") } ' } # 追加実行用関数 # funcout "perr" "fname" funcout() { printf '%s () { \n' "$pfunc" printf 'if [ "$1" = "-d" ] ; then\n' printf ' eval "%s${2#?}"\047="$3"\047 # opt_?="$3"\n' "$optpre" printf ' return 0\nfi\n\n' printf '%s="$2"\n' "$popt" cat << 'EEE' if [ "$1" = "-e" ] ; then shift 4 while [ $# -gt 0 ] do if [ "$1" != "${1#[#]e}" ] || [ "$1" != "${1#[#]E}" ] ; then eval "${1#[#]?}" if [ "$?" != "0" ] ; then EEE printf ' OPTARG="errmsg $%s $1"\n' "$popt" cat << 'EEE' return 1 fi fi shift done return 0 fi if [ "$1" = "-c" ] ; then shift 3 printf '%s\n' "$1" | tr '[:upper:]' '[:lower:]' | awk '$1 ~ /int/ {exit 1}' if [ "$?" = "1" ] ; then shift set -- "dummy" 'printf "%d" "$OPTARG" >/dev/null 2>&1' "$@" fi shift while [ $# -gt 0 ] do eval "$1" if [ "$?" != "0" ] ; then EEE printf ' OPTARG="errmsg $%s $1"\n' "$popt" cat << 'EEE' return 1 fi shift done return 0 fi echo "$0: bug. sleep" >/dev/stderr while : do sleep 1000 done exit 1 } EEE } #---main # 5ms rawlist="$1" shift # ...ck quite ':' qerr=0 printf '%s\n' "$rawlist" | awk 'NR == 1 && $1 ~ /^:/ {exit 1}' if [ "$?" = "1" ] ; then qerr=1 rawlist=`printf '%s\n' "$rawlist" | awk 'NR == 1 {sub(/:/, "", $1)} {print $0}'` fi # ...ck_posixopt -a -f -? ..etc. rtn err opt buf=`printf '%s\n' "$rawlist" | awk '$1 != "" && $1 !~ /^#/ {print $1} ' ` set -- $buf rtn=`eval "ck_posixopt $@" ` # rtn ... erropt -s etc if [ "$?" != "0" ] ; then printf 'OPTARG="errmsg %s invalid opt is defined"\n' "$rtn" test "$qerr" = "0" || printf 'echo "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done\n' printf 'test 1 = 0\n' exit 1 fi # 13ms fmt=`func_getfmt "$rawlist"` # どっかのリストで要素が足りない。要素はchangelist内部で分解されるが3つ以上欲しい。 # エラー意思表示として異常fmtを出力。 if [ "$fmt" != "${fmt#*::*}" ] ; then buf=`echo "$fmt" | sed -e 's/.*\(.\)::.*/-\1/g'` printf 'OPTARG="errmsg %s invalid optlist format. (bug)"\n' "$buf" test "$qerr" = "0" || printf 'echo "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done\n' printf 'test 1 = 0\n' exit 1 fi # 16ms cmdlist=`func_execlist "$rawlist" ` buf=`printf '%s\n' "$cmdlist" | awk '$1 ~ /^#/ {$1=""; print $0}'` # info out dfldata=" OPTIND=1 # fixed value. posix rule. OPTERR=0 ${pbuf}='' ${popt}='' $buf" # output dflsetdata (opt_b="0" etc) printf "## ---this code is generated by $lname\n" printf " ---optsetting %s" "$rawlist" | awk '{print "#" $0}' echo funcout printf "\n# ---dflset\n" printf '%s\n' "$dfldata" # main out cat << 'EEE' #---getopts loop. break if all args read or detect '--' while : do EEE printf 'if [ 0 -eq "$#" ] || [ "%s" = "errmsg" ] ; then\n' '${OPTARG%% *}' cat << 'EEE' break fi check_opt='$'"$OPTIND" eval "check_opt=\"$check_opt\"" if [ "$check_opt" = "--" ] ; then shift $OPTIND; break fi EEE printf 'getopts ":%s" %s "$@" # ":a:bc:f:" etc...\n' "$fmt" "$popt" # popt:getopts :a ckopt_opt "$@" cat << 'EEE' if [ "$?" = "1" ] ; then # detect end/normal args. save general args. shift $((OPTIND - 1)) if [ "$#" -eq "0" ] ; then break fi EEE # skip系 # ckopt_buf="ckopt_ar$ckopt_delcnt" # ar1, 2... # eval "$ckopt_buf="'"$1"' # ar3="$1" # ckopt_delskip="$ckopt_delskip ""$ckopt_buf""='' ;" # ckopt_buf='"$'"$ckopt_buf"'"' # ar1 -> "$ar1" # ckopt_skip="$ckopt_skip $ckopt_buf" # ckopt_delcnt=$((ckopt_delcnt + 1 )) # shift # OPTIND=1 # continue printf ' %s="$%s "' "$pbuf" "$pbuf" cat << 'EEE' `printf '%s' "$1" | sed -e "{ s#'#'\"'\"'#g s/^/'/g s/$/'/g }"` shift OPTIND=1 continue fi # --- your setting EEE printf '%s\n' "$cmdlist" | awk '$1 !~ /^#/ {print $0}' printf '# --- your setting end\n' cat << 'EEE' # hit err. ckopt chars... # err detect@silent mode # $?=1 ... detect optend or '--' # ':' ... detect option, but dont have subargs (OPTARG="factor"). # '?' ... detect unsupported option char (OPTARG="factor") or args end (OPTARG="blank"). # OPTARG ... "" is optend. "a/b/c..." is invalid option # OPTIND ... if err, OPTIND indicates next (new) arg pos. OPTARG="errmsg -$OPTARG invalid option / misses subargs" done # --- post process. set not optional args & clean & #eE last cmd. for ii in 1 # dummyjump logic do OPTIND=1 # skip test "${OPTARG%% *}" != "errmsg" || break EEE printf ' %s="set -- $%s"\047 "$@"\047 # set -- \047cmd\047 .. "$@"\n' "$pbuf" "$pbuf" printf ' eval "$%s"\n' "$pbuf" # %s, no quote. presed printf '\n # run #E cmd. cmd + test $? ... xn\n' printf '%s\n' "$cmdlist" | awk '$1 ~ /^#/ {$1=""; $3="-e" ; print $0}' cat << 'EEE' done if [ "${OPTARG%% *}" = "errmsg" ] ; then EEE if [ "$qerr" = "0" ] ; then cat << 'EEE' printf "$0: opterr. %s\n" "$OPTARG" >/dev/stderr ; while : ; do sleep 1000 ; done ; exit 1 EEE fi printf ' test 1 = 0\nfi\n' printf "## ---generate by $lname end\n" exit