Bashでコマンドライン引数を解析するにはどうすればよいですか?

2008-10-11 bash command-line scripting arguments getopts

たとえば、次の行で呼び出されるスクリプトがあるとします。

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

またはこれ:

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

それぞれの場合(または2つの組み合わせ)で$v$f 、および$dがすべてtrueに設定され、 $outFile/fizz/someOtherFileと等しくなるように、これを解析する受け入れられた方法は何ですか?

Answers

getopt() / getopts()は良いオプションです。 ここから盗まれ

「getopt」の簡単な使用法は、このミニスクリプトに示されています。

#!/bin/bash
echo "Before getopt"
for i
do
  echo $i
done
args=`getopt abc:d $*`
set -- $args
echo "After getopt"
for i
do
  echo "-->$i"
done

私たちが言ったことは、-a、 -b、-cまたは-dは許可されますが、-cの後に引数が続きます(「c:」はそれを示します)。

これを「g」と呼んで試してみると:

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

2つの引数から始めます。 「getopt」はオプションを分解し、 それぞれを独自の引数に入れます。また 「-」を追加しました。

これは使用するのに十分簡単だと思います:

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ $rc$opt == 0? ]&&exit 1;[ $rc == 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval $readopt
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

呼び出し例:

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

from: digitalpeer.comに小さな変更を加えたもの

使用法myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "[email protected]"
do
case $i in
    -p=*|--prefix=*)
    PREFIX="${i#*=}"

    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    ;;
    -l=*|--lib=*)
    DIR="${i#*=}"
    ;;
    --default)
    DEFAULT=YES
    ;;
    *)
            # unknown option
    ;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}

このガイドの 「部分文字列の削除」で${i#*=}検索することを理解するために。これは、不要なサブプロセスまたは`echo "$i" | sed 's/[^=]*=//'`を呼び出す`sed 's/[^=]*=//' <<< "$i"`と機能的に同等です`echo "$i" | sed 's/[^=]*=//'`2つの不要なサブプロセスを呼び出します。

方法#1:getopt [s]なしでbashを使用する

キーと値のペアの引数を渡す一般的な方法は次の2つです。

スペースで区切られたBash(例--option argument )(getopt [s]なし)

使用法demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"

case $key in
    -e|--extension)
    EXTENSION="$2"
    shift # past argument
    shift # past value
    ;;
    -s|--searchpath)
    SEARCHPATH="$2"
    shift # past argument
    shift # past value
    ;;
    -l|--lib)
    LIBPATH="$2"
    shift # past argument
    shift # past value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument
    ;;
    *)    # unknown option
    POSITIONAL+=("$1") # save it in an array for later
    shift # past argument
    ;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 "$1"
fi
EOF

chmod +x /tmp/demo-space-separated.sh

/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts

上記のブロックのコピー貼り付けからの出力:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

Bash Equals-Separated(eg --option=argument )(getopt [s]なし)

使用法demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash

for i in "[email protected]"
do
case $i in
    -e=*|--extension=*)
    EXTENSION="${i#*=}"
    shift # past argument=value
    ;;
    -s=*|--searchpath=*)
    SEARCHPATH="${i#*=}"
    shift # past argument=value
    ;;
    -l=*|--lib=*)
    LIBPATH="${i#*=}"
    shift # past argument=value
    ;;
    --default)
    DEFAULT=YES
    shift # past argument with no value
    ;;
    *)
          # unknown option
    ;;
esac
done
echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
echo "LIBRARY PATH    = ${LIBPATH}"
echo "DEFAULT         = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
    echo "Last line of file specified as non-opt/last argument:"
    tail -1 $1
fi
EOF

chmod +x /tmp/demo-equals-separated.sh

/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts

上記のブロックのコピー貼り付けからの出力:

FILE EXTENSION  = conf
SEARCH PATH     = /etc
LIBRARY PATH    = /usr/lib
DEFAULT         =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34    example.com

このガイドの 「部分文字列の削除」で${i#*=}検索することを理解するために。これは、不要なサブプロセスまたは`echo "$i" | sed 's/[^=]*=//'`を呼び出す`sed 's/[^=]*=//' <<< "$i"`と機能的に同等です`echo "$i" | sed 's/[^=]*=//'`2つの不要なサブプロセスを呼び出します。

方法#2:getopt [s]でbashを使用する

から: http : //mywiki.wooledge.org/BashFAQ/035#getopts

getopt(1)の制限 (古い、比較的最近のgetoptバージョン):

  • 空の文字列である引数を処理できません
  • 空白が埋め込まれた引数を処理できません

最近のgetoptバージョンには、これらの制限はありません。

さらに、POSIXシェル(およびその他)は、これらの制限のないgetoptsを提供します。単純なgetopts例を含めました。

使用法demo-getopts.sh -vf /etc/hosts foo bar

cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh

# A POSIX variable
OPTIND=1         # Reset in case getopts has been used previously in the shell.

# Initialize our own variables:
output_file=""
verbose=0

while getopts "h?vf:" opt; do
    case "$opt" in
    h|\?)
        show_help
        exit 0
        ;;
    v)  verbose=1
        ;;
    f)  output_file=$OPTARG
        ;;
    esac
done

shift $((OPTIND-1))

[ "${1:-}" = "--" ] && shift

echo "verbose=$verbose, output_file='$output_file', Leftovers: [email protected]"
EOF

chmod +x /tmp/demo-getopts.sh

/tmp/demo-getopts.sh -vf /etc/hosts foo bar

上記のブロックのコピー貼り付けからの出力:

verbose=1, output_file='/etc/hosts', Leftovers: foo bar

getoptsの利点は次のとおりです。

  1. 移植性が高く、 dashような他のシェルでも動作します。
  2. 通常のUnixの方法で、 -vf filenameなどの複数の単一オプションを-vf filename処理できます。

getoptsの欠点は、追加のコードなしでは短いオプション( --helpではなく-h )しか処理できないことです。

すべての構文と変数の意味を説明するgetoptsチュートリアルがあります。 bashには、参考になる可能性があるhelp getoptsもあります。

bash-modulesのモジュール「引数」を使用する

例:

#!/bin/bash
. import.sh log arguments

NAME="world"

parse_arguments "-n|--name)NAME;S" -- "[email protected]" || {
  error "Cannot parse command line."
  exit 1
}

info "Hello, $NAME!"

これは、スタック内のより高い場所で同時に実行されるgetoptsを壊さないようにするための関数での方法です。

function waitForWeb () {
   local OPTIND=1 OPTARG OPTION
   local host=localhost port=8080 proto=http
   while getopts "h:p:r:" OPTION; do
      case "$OPTION" in
      h)
         host="$OPTARG"
         ;;
      p)
         port="$OPTARG"
         ;;
      r)
         proto="$OPTARG"
         ;;
      esac
   done
...
}

@guneysusによる優れた答えを拡張して、ユーザーが好きな構文を使用できるようにする微調整を以下に示します。

command -x=myfilename.ext --another_switch 

command -x myfilename.ext --another_switch

つまり、等号は空白で置き換えることができます。

この「あいまいな解釈」は好みに合わないかもしれませんが、他のユーティリティと互換性のあるスクリプトを作成している場合(私の場合、ffmpegで動作する必要がある)、柔軟性が役立ちます。

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "[email protected]"
do
  case "${prefix}${keyValue}" in
    -i=*|--input_filename=*)  key="-i";     value="${keyValue#*=}";; 
    -ss=*|--seek_from=*)      key="-ss";    value="${keyValue#*=}";;
    -t=*|--play_seconds=*)    key="-t";     value="${keyValue#*=}";;
    -|--stdin)                key="-";      value=1;;
    *)                                      value=$keyValue;;
  esac
  case $key in
    -i) MOVIE=$(resolveMovie "${value}");  prefix=""; key="";;
    -ss) SEEK_FROM="${value}";          prefix=""; key="";;
    -t)  PLAY_SECONDS="${value}";           prefix=""; key="";;
    -)   STD_IN=${value};                   prefix=""; key="";; 
    *)   prefix="${keyValue}=";;
  esac
done

これも知っておくと便利です。値を設定し、誰かが入力を提供した場合は、その値でデフォルトを上書きできます。

myscript.sh -f ./serverlist.txtまたは単に./myscript.sh(およびデフォルトを使用)

    #!/bin/bash
    # --- set the value, if there is inputs, override the defaults.

    HOME_FOLDER="${HOME}/owned_id_checker"
    SERVER_FILE_LIST="${HOME_FOLDER}/server_list.txt"

    while [[ $# > 1 ]]
    do
    key="$1"
    shift

    case $key in
        -i|--inputlist)
        SERVER_FILE_LIST="$1"
        shift
        ;;
    esac
    done


    echo "SERVER LIST   = ${SERVER_FILE_LIST}"

私はこの質問に約4年遅れていますが、返したいと思います。以前の回答を出発点として使用して、古いアドホックパラメータの解析を整理しました。次に、次のテンプレートコードをリファクタリングしました。 =またはスペースで区切られた引数、およびグループ化された複数の短いパラメーターを使用して、長いパラメーターと短いパラメーターの両方を処理します。最後に、param以外の引数を$ 1、$ 2 ..変数に再挿入します。お役に立てれば幸いです。

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 [email protected] ; exit $? ; fi

echo "Before"
for i ; do echo - $i ; done


# Code template for parsing command line parameters using only portable shell
# code, while handling both long and short params, handling '-f file' and
# '-f=file' style param data and also capturing non-parameters to be inserted
# back into the shell positional parameters.

while [ -n "$1" ]; do
        # Copy so we can modify it (can't modify $1)
        OPT="$1"
        # Detect argument termination
        if [ x"$OPT" = x"--" ]; then
                shift
                for OPT ; do
                        REMAINS="$REMAINS \"$OPT\""
                done
                break
        fi
        # Parse current opt
        while [ x"$OPT" != x"-" ] ; do
                case "$OPT" in
                        # Handle --flag=value opts like this
                        -c=* | --config=* )
                                CONFIGFILE="${OPT#*=}"
                                shift
                                ;;
                        # and --flag value opts like this
                        -c* | --config )
                                CONFIGFILE="$2"
                                shift
                                ;;
                        -f* | --force )
                                FORCE=true
                                ;;
                        -r* | --retry )
                                RETRY=true
                                ;;
                        # Anything unknown is recorded for later
                        * )
                                REMAINS="$REMAINS \"$OPT\""
                                break
                                ;;
                esac
                # Check for multiple short options
                # NOTICE: be sure to update this pattern to match valid options
                NEXTOPT="${OPT#-[cfr]}" # try removing single short opt
                if [ x"$OPT" != x"$NEXTOPT" ] ; then
                        OPT="-$NEXTOPT"  # multiple short opts, keep going
                else
                        break  # long form, exit inner loop
                fi
        done
        # Done with that param. move to next
        shift
done
# Set the non-parameters back into the positional parameters ($1 $2 ..)
eval set -- $REMAINS


echo -e "After: \n configfile='$CONFIGFILE' \n force='$FORCE' \n retry='$RETRY' \n remains='$REMAINS'"
for i ; do echo - $i ; done

getoptsは、#1をインストールし、#2を同じプラットフォームで実行する場合に適切に機能します。 OSXとLinux(たとえば)は、この点で動作が異なります。

これは、等しい、等しくない、ブールフラグをサポートする(非getopts)ソリューションです。たとえば、次のようにスクリプトを実行できます。

./script --arg1=value1 --arg2 value2 --shouldClean

# parse the arguments.
COUNTER=0
ARGS=("[email protected]")
while [ $COUNTER -lt $# ]
do
    arg=${ARGS[$COUNTER]}
    let COUNTER=COUNTER+1
    nextArg=${ARGS[$COUNTER]}

    if [[ $skipNext -eq 1 ]]; then
        echo "Skipping"
        skipNext=0
        continue
    fi

    argKey=""
    argVal=""
    if [[ "$arg" =~ ^\- ]]; then
        # if the format is: -key=value
        if [[ "$arg" =~ \= ]]; then
            argVal=$(echo "$arg" | cut -d'=' -f2)
            argKey=$(echo "$arg" | cut -d'=' -f1)
            skipNext=0

        # if the format is: -key value
        elif [[ ! "$nextArg" =~ ^\- ]]; then
            argKey="$arg"
            argVal="$nextArg"
            skipNext=1

        # if the format is: -key (a boolean flag)
        elif [[ "$nextArg" =~ ^\- ]] || [[ -z "$nextArg" ]]; then
            argKey="$arg"
            argVal=""
            skipNext=0
        fi
    # if the format has not flag, just a value.
    else
        argKey=""
        argVal="$arg"
        skipNext=0
    fi

    case "$argKey" in 
        --source-scmurl)
            SOURCE_URL="$argVal"
        ;;
        --dest-scmurl)
            DEST_URL="$argVal"
        ;;
        --version-num)
            VERSION_NUM="$argVal"
        ;;
        -c|--clean)
            CLEAN_BEFORE_START="1"
        ;;
        -h|--help|-help|--h)
            showUsage
            exit
        ;;
    esac
done

拡張されたgetoptについて言及している回答はありません。また、 トップ投票の答えは誤解を招くものです。これは、 -⁠vfdスタイルの短いオプション(OPによって要求される)または位置引数の後のオプション(OPによっても要求される)を無視します。そしてそれは構文解析エラーを無視します。代わりに:

  • util-linuxまたは以前のGNU glibcから拡張されたgetoptを使用します。 1
  • GNU glibcのC関数であるgetopt_long()動作します。
  • すべての便利な識別機能があります(他の機能にはありません)。
    • 引数2でスペース、引用符、さらにはバイナリを処理します(拡張されていないgetoptできません)
    • 最後にオプションを処理できます: script.sh -o outFile file1 file2 -vgetoptsはこれを行いません)
    • 許可= script.sh --outfile=fileOut --infile fileIn長いオプション: script.sh --outfile=fileOut --infile fileIn (自己解析の場合、両方を許可すると時間がかかります)
    • -vfd短いオプションを組み合わせることができます(自己解析の場合は実際の作業)
    • -oOutfile-vfdoOutfileなどのオプション引数に触れることができます
  • 非常に古く3なので、GNUシステムにはこれがありません(たとえば、どのLinuxにもあります)。
  • getopt --test →戻り値4でその存在をテストできます。
  • 他のgetoptまたはシェル組み込みのgetopts使用は制限されています。

次の呼び出し

myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile

すべてのリターン

verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile

次のmyscript

#!/bin/bash
# saner programming env: these switches turn some bugs into errors
set -o errexit -o pipefail -o noclobber -o nounset

# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null 
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
    echo 'I’m sorry, `getopt --test` failed in this environment.'
    exit 1
fi

OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose

# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via   -- "[email protected]"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "[email protected]")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
    # e.g. return value is 1
    #  then getopt has complained about wrong arguments to stdout
    exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"

d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
    case "$1" in
        -d|--debug)
            d=y
            shift
            ;;
        -f|--force)
            f=y
            shift
            ;;
        -v|--verbose)
            v=y
            shift
            ;;
        -o|--output)
            outFile="$2"
            shift 2
            ;;
        --)
            shift
            break
            ;;
        *)
            echo "Programming error"
            exit 3
            ;;
    esac
done

# handle non-option arguments
if [[ $# -ne 1 ]]; then
    echo "$0: A single input file is required."
    exit 4
fi

echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"

1つの拡張getoptは、Cygwinを含むほとんどの「bashシステム」で使用できます。 OS Xでは、 brew install gnu-getoptまたはsudo port install getopt
2 POSIX exec()規則には、コマンドライン引数でバイナリNULLを渡す信頼できる方法がありません。これらのバイトは引数を途中で終了します
1997年以前にリリースされた3つの最初のバージョン(1997年まで追跡しただけです)

位置引数とフラグベースの引数の混在

--param = arg(区切り文字と等しい)

位置引数間でフラグを自由に混ぜる:

./script.sh dumbo 127.0.0.1 --environment=production -q -d
./script.sh dumbo --environment=production 127.0.0.1 --quiet -d

かなり簡潔なアプローチで達成できます:

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   param=${!pointer}
   if [[ $param != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      case $param in
         # paramter-flags with arguments
         -e=*|--environment=*) environment="${param#*=}";;
                  --another=*) another="${param#*=}";;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + 1)):$#} \
         || set -- ${@:((pointer + 1)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

--param arg(スペース区切り)

--flag=value--flag valueスタイルを混在させないほうが通常は明確です。

./script.sh dumbo 127.0.0.1 --environment production -q -d

これは読むのが難しいですが、まだ有効です

./script.sh dumbo --environment production 127.0.0.1 --quiet -d

ソース

# process flags
pointer=1
while [[ $pointer -le $# ]]; do
   if [[ ${!pointer} != "-"* ]]; then ((pointer++)) # not a parameter flag so advance pointer
   else
      param=${!pointer}
      ((pointer_plus = pointer + 1))
      slice_len=1

      case $param in
         # paramter-flags with arguments
         -e|--environment) environment=${!pointer_plus}; ((slice_len++));;
                --another) another=${!pointer_plus}; ((slice_len++));;

         # binary flags
         -q|--quiet) quiet=true;;
                 -d) debug=true;;
      esac

      # splice out pointer frame from positional list
      [[ $pointer -gt 1 ]] \
         && set -- ${@:1:((pointer - 1))} ${@:((pointer + $slice_len)):$#} \
         || set -- ${@:((pointer + $slice_len)):$#};
   fi
done

# positional remain
node_name=$1
ip_address=$2

私のバージョンのオプション解析を提供したいと思います。これにより、次のことが可能になります。

-s p1
--stage p1
-w somefolder
--workfolder somefolder
-sw p1 somefolder
-e=hello

これも可能にします(望ましくない可能性があります):

-s--workfolder p1 somefolder
-se=hello p1
-swe=hello p1 somefolder

=をオプションで使用するかどうかを使用前に決定する必要があります。これはコードをクリーンに保つためです。

while [[ $# > 0 ]]
do
    key="$1"
    while [[ ${key+x} ]]
    do
        case $key in
            -s*|--stage)
                STAGE="$2"
                shift # option has parameter
                ;;
            -w*|--workfolder)
                workfolder="$2"
                shift # option has parameter
                ;;
            -e=*)
                EXAMPLE="${key#*=}"
                break # option has been fully handled
                ;;
            *)
                # unknown option
                echo Unknown option: $key #1>&2
                exit 10 # either this: my preferred way to handle unknown options
                break # or this: do this to signal the option has been handled (if exit isn't used)
                ;;
        esac
        # prepare for next option in this key, if any
        [[ "$key" = -? || "$key" == --* ]] && unset key || key="${key/#-?/-}"
    done
    shift # option(s) fully processed, proceed to next input argument
done

無視する別の例を追加するリスクがあるので、これが私のスキームです。

  • -n argおよび--name=arg処理します
  • 最後に引数を許可します
  • スペルに誤りがある場合は、正しいエラーを表示します
  • 互換性あり、バシズムを使用しない
  • 可読、ループで状態を維持する必要はありません

それが誰かに役立つことを願っています。

while [ "$#" -gt 0 ]; do
  case "$1" in
    -n) name="$2"; shift 2;;
    -p) pidfile="$2"; shift 2;;
    -l) logfile="$2"; shift 2;;

    --name=*) name="${1#*=}"; shift 1;;
    --pidfile=*) pidfile="${1#*=}"; shift 1;;
    --logfile=*) logfile="${1#*=}"; shift 1;;
    --name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;

    -*) echo "unknown option: $1" >&2; exit 1;;
    *) handle_argument "$1"; shift 1;;
  esac
done

これが、変数配列を使用したブルーノブロノスキーの答えの改善されたソリューションです。

パラメータの位置を混合し、オプションなしで順序を維持するパラメータ配列を提供できます

#!/bin/bash

echo [email protected]

PARAMS=()
SOFT=0
SKIP=()
for i in "[email protected]"
do
case $i in
    -n=*|--skip=*)
    SKIP+=("${i#*=}")
    ;;
    -s|--soft)
    SOFT=1
    ;;
    *)
        # unknown option
        PARAMS+=("$i")
    ;;
esac
done
echo "SKIP            = ${SKIP[@]}"
echo "SOFT            = $SOFT"
    echo "Parameters:"
    echo ${PARAMS[@]}

例えば出力します:

$ ./test.sh parameter -s somefile --skip=.c --skip=.obj
parameter -s somefile --skip=.c --skip=.obj
SKIP            = .c .obj
SOFT            = 1
Parameters:
parameter somefile

getopt [s]、POSIX、古いUnixスタイルなしの別のソリューション

Bruno Bronoskyがここに投稿たソリューションと同様に getopt(s)使用しないソリューションです。

私のソリューションの主な差別化機能は、 tar -xzf foo.tar.gztar -x -z -f foo.tar.gzと同じであるように、オプションを連結できることtar -x -z -f foo.tar.gztarpsなどと同様に、短いオプションのブロックでは先頭のハイフンはオプションです(ただし、これは簡単に変更できます)。長いオプションもサポートされています(ただし、ブロックが1つで始まる場合、2つの先行ハイフンが必要です)。

サンプルオプションを含むコード

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from [email protected][se.unix]"
echo

print_usage() {
  echo "Usage:

  $0 {a|b|c} [ARG...]

Options:

  --aaa-0-args
  -a
    Option without arguments.

  --bbb-1-args ARG
  -b ARG
    Option with one argument.

  --ccc-2-args ARG1 ARG2
  -c ARG1 ARG2
    Option with two arguments.

" >&2
}

if [ $# -le 0 ]; then
  print_usage
  exit 1
fi

opt=
while :; do

  if [ $# -le 0 ]; then

    # no parameters remaining -> end option parsing
    break

  elif [ ! "$opt" ]; then

    # we are at the beginning of a fresh block
    # remove optional leading hyphen and strip trailing whitespaces
    opt=$(echo "$1" | sed 's/^-\?\([a-zA-Z0-9\?-]*\)/\1/')

  fi

  # get the first character -> check whether long option
  first_chr=$(echo "$opt" | awk '{print substr($1, 1, 1)}')
  [ "$first_chr" = - ] && long_option=T || long_option=F

  # note to write the options here with a leading hyphen less
  # also do not forget to end short options with a star
  case $opt in

    -)

      # end of options
      shift
      break
      ;;

    a*|-aaa-0-args)

      echo "Option AAA activated!"
      ;;

    b*|-bbb-1-args)

      if [ "$2" ]; then
        echo "Option BBB with argument '$2' activated!"
        shift
      else
        echo "BBB parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    c*|-ccc-2-args)

      if [ "$2" ] && [ "$3" ]; then
        echo "Option CCC with arguments '$2' and '$3' activated!"
        shift 2
      else
        echo "CCC parameters incomplete!" >&2
        print_usage
        exit 1
      fi
      ;;

    h*|\?*|-help)

      print_usage
      exit 0
      ;;

    *)

      if [ "$long_option" = T ]; then
        opt=$(echo "$opt" | awk '{print substr($1, 2)}')
      else
        opt=$first_chr
      fi
      printf 'Error: Unknown option: "%s"\n' "$opt" >&2
      print_usage
      exit 1
      ;;

  esac

  if [ "$long_option" = T ]; then

    # if we had a long option then we are going to get a new block next
    shift
    opt=

  else

    # if we had a short option then just move to the next character
    opt=$(echo "$opt" | awk '{print substr($1, 2)}')

    # if block is now empty then shift to the next one
    [ "$opt" ] || shift

  fi

done

echo "Doing something..."

exit 0

使用例については、以下の例を参照してください。

引数付きのオプションの位置

価値があるのは、引数付きのオプションが最後になるわけではないことです(長いオプションだけが必要です)。したがって、たとえばtar (少なくとも一部の実装では)ファイル名が続くため、 fオプションは最後にする必要があります( tar xzf bar.tar.gz機能しますが、 tar xfz bar.tar.gzは機能しません)これはここではtar xfz bar.tar.gzません。 (後の例を参照してください)。

引数付きの複数のオプション

別のボーナスとして、オプションパラメータは、必要なオプションを持つパラメータによってオプションの順序で消費されます。コマンドラインabc XYZ (または-abc XYZ )を使用して、ここでスクリプトの出力を確認してください。

Option AAA activated!
Option BBB with argument 'X' activated!
Option CCC with arguments 'Y' and 'Z' activated!

長いオプションも連結されます

また、オプションブロックに長いオプションが含まれている場合は、それらをブロックの最後に配置することもできます。したがって、次のコマンドラインはすべて同等です(オプションとその引数が処理される順序を含む)。

  • -cba ZYX
  • cba ZYX
  • -cb-aaa-0-args ZYX
  • -c-bbb-1-args ZYX -a
  • --ccc-2-args ZY -ba X
  • c ZY b X a
  • -c ZY -b X -a
  • --ccc-2-args ZY --bbb-1-args X --aaa-0-args

これらすべてが次の原因になります。

Option CCC with arguments 'Z' and 'Y' activated!
Option BBB with argument 'X' activated!
Option AAA activated!
Doing something...

このソリューションにはありません

オプションの引数

オプションの引数を持つオプションは、少しの作業で可能になるはずです。たとえば、ハイフンのないブロックがあるかどうかを楽しみにしています。ユーザーは、オプションのパラメーターを持つパラメーターを持つブロックに続くすべてのブロックの前にハイフンを置く必要があります。おそらくこれは複雑すぎてユーザーに伝えることができないので、この場合は先頭のハイフンを完全に必要とするだけです。

複数の可能なパラメーターがあるため、状況はさらに複雑になります。将来的には機能しなくなる可能性があるため、引数がその引数に対するものであるかどうかを判断して、オプションをスマートにしようとしないことをお勧めします(たとえば、オプションを使用すると、オプションの引数として数値をとるだけです)。

私はオプションの引数ではなく、追加のオプションを個人的に支持しています。

等号で導入されたオプション引数

オプションの引数と同じように、私はこれのファンではありません(ところで、さまざまなパラメータースタイルの賛否両論について議論するためのスレッドはありますか?)しかし、これが必要な場合は、 http:// mywiki.wooledge.org/BashFAQ/035#Manual_loop with --long-with-arg=?* case statement and --long-with-arg=?* the equal sign(これはBTWのサイトですが、パラメータの連結を行うことはいくつかの努力で可能であると述べていますが、 "読者のための練習問題として残しました」と言ったので、彼らは彼らの言葉を引き受けましたが、ゼロから始めました)。

その他の注意事項

POSIX準拠、私が対処しなければならなかった古代のBusyboxセットアップでも機能します(たとえば、 cutheadgetopts欠落している場合)。

getopt(1)は、AT&Tの短命な誤りであったことに注意してください。

getoptは1984年に作成されましたが、実際には使用できなかったため、1986年にすでに埋葬されています。

事実の証明getopt非常に古くなっていることがあるgetopt(1)のmanページはまだ言及し"$*"の代わりに"[email protected]"と一緒に1986年のBourneシェルに加えて、 getopts(1)シェル組み込み内部にスペースがある引数を処理するため。

ところで、シェルスクリプトで長いオプションを解析することに興味がある場合は、libc(Solaris)のgetopt(3)実装とksh93両方が、長いオプションをサポートする統一された長いオプション実装をshortのエイリアスとして追加したことを知っておくとよいでしょう。オプション。これにより、 ksh93Bourne Shell getoptsを介して長いオプション用の統一されたインターフェースを実装します。

Bourne Shellのmanページから取られた長いオプションの例:

getopts "f:(file)(input-file)o:(output-file)" OPTX "[email protected]"

は、Bourne Shellとksh93の両方でオプションエイリアスを使用できる期間を示しています。

最近のBourne Shellのmanページを見てください:

http://schillix.sourceforge.net/man/man1/bosh.1.html

OpenSolarisのgetopt(3)のmanページ:

http://schillix.sourceforge.net/man/man3c/getopt.3c.html

最後に、古い$ *を確認するためのgetopt(1)のマニュアルページ:

http://schillix.sourceforge.net/man/man1/getopt.1.html

より簡潔な方法

script.sh

#!/bin/bash

while [[ "$#" -gt 0 ]]; do
    case $1 in
        -d|--deploy) deploy="$2"; shift ;;
        -u|--uglify) uglify=1 ;;
        *) echo "Unknown parameter passed: $1"; exit 1 ;;
    esac
    shift
done

echo "Should deploy? $deploy"
echo "Should uglify? $uglify"

使用法:

./script.sh -d dev -u

# OR:

./script.sh --deploy dev --uglify

コマンドラインからparamsを解析する関数parse_paramsを提供します。

  1. これは純粋なBashソリューションであり、追加のユーティリティはありません。
  2. グローバルスコープを汚染しません。
  3. 簡単に使用できる変数が簡単に返されるので、さらにロジックを構築できます。
  4. paramsの前のダッシュの量は関係ありません( --all equals -all equals all=all

以下のスクリプトは、コピーと貼り付けのデモです。参照してくださいshow_use使用方法を理解するための機能をparse_params

制限:

  1. スペースで区切られたパラメーターをサポートしていません( -d 1
  2. --any-param名はダッシュを失うため、-- --any-param-anyparamは同等です
  3. eval $(parse_params "[email protected]")はbash 関数内で使用する必要があります(グローバルスコープでは機能しません)

#!/bin/bash

# Universal Bash parameter parsing
# Parse equal sign separated params into named local variables
# Standalone named parameter value will equal its param name (--force creates variable $force=="force")
# Parses multi-valued named params into an array (--path=path1 --path=path2 creates ${path[*]} array)
# Puts un-named params as-is into ${ARGV[*]} array
# Additionally puts all named params as-is into ${ARGN[*]} array
# Additionally puts all standalone "option" params as-is into ${ARGO[*]} array
# @author Oleksii Chekulaiev
# @version v1.4.1 (Jul-27-2018)
parse_params ()
{
    local existing_named
    local ARGV=() # un-named params
    local ARGN=() # named params
    local ARGO=() # options (--params)
    echo "local ARGV=(); local ARGN=(); local ARGO=();"
    while [[ "$1" != "" ]]; do
        # Escape asterisk to prevent bash asterisk expansion, and quotes to prevent string breakage
        _escaped=${1/\*/\'\"*\"\'}
        _escaped=${_escaped//\'/\\\'}
        _escaped=${_escaped//\"/\\\"}
        # If equals delimited named parameter
        nonspace="[^[:space:]]"
        if [[ "$1" =~ ^${nonspace}${nonspace}*=..* ]]; then
            # Add to named parameters array
            echo "ARGN+=('$_escaped');"
            # key is part before first =
            local _key=$(echo "$1" | cut -d = -f 1)
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # val is everything after key and = (protect from param==value error)
            local _val="${1/$_key=}"
            # remove dashes from key name
            _key=${_key//\-}
            # skip when key is empty
            # search for existing parameter name
            if (echo "$existing_named" | grep "\b$_key\b" >/dev/null); then
                # if name already exists then it's a multi-value named parameter
                # re-declare it as an array if needed
                if ! (declare -p _key 2> /dev/null | grep -q 'declare \-a'); then
                    echo "$_key=(\"\$$_key\");"
                fi
                # append new value
                echo "$_key+=('$_val');"
            else
                # single-value named parameter
                echo "local $_key='$_val';"
                existing_named=" $_key"
            fi
        # If standalone named parameter
        elif [[ "$1" =~ ^\-${nonspace}+ ]]; then
            # remove dashes
            local _key=${1//\-}
            # Just add as non-named when key is empty or contains space
            if [[ "$_key" == "" || "$_key" =~ " " ]]; then
                echo "ARGV+=('$_escaped');"
                shift
                continue
            fi
            # Add to options array
            echo "ARGO+=('$_escaped');"
            echo "local $_key=\"$_key\";"
        # non-named parameter
        else
            # Escape asterisk to prevent bash asterisk expansion
            _escaped=${1/\*/\'\"*\"\'}
            echo "ARGV+=('$_escaped');"
        fi
        shift
    done
}

#--------------------------- DEMO OF THE USAGE -------------------------------

show_use ()
{
    eval $(parse_params "[email protected]")
    # --
    echo "${ARGV[0]}" # print first unnamed param
    echo "${ARGV[1]}" # print second unnamed param
    echo "${ARGN[0]}" # print first named param
    echo "${ARG0[0]}" # print first option param (--force)
    echo "$anyparam"  # print --anyparam value
    echo "$k"         # print k=5 value
    echo "${multivalue[0]}" # print first value of multi-value
    echo "${multivalue[1]}" # print second value of multi-value
    [[ "$force" == "force" ]] && echo "\$force is set so let the force be with you"
}

show_use "param 1" --anyparam="my value" param2 k=5 --force --multi-value=test1 --multi-value=test2

EasyOptionsは解析を必要としません。

## Options:
##   --verbose, -v  Verbose mode
##   --output=FILE  Output filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "output file is ${output}"
    echo "${arguments[@]}"
fi

スクリプトで移植可能な解析を作成する問題が非常にイライラするので、 Argbash-スクリプトの引数解析コードを生成できるFOSSコードジェネレーターと、いくつかの優れた機能を記述しました。

https://argbash.io

私が試したとき、この質問に対する一番上の答えは少しバグが多いように見えました-ここに私がより堅牢であることがわかった私の解決策があります:

boolean_arg=""
arg_with_value=""

while [[ $# -gt 0 ]]
do
key="$1"
case $key in
    -b|--boolean-arg)
    boolean_arg=true
    shift
    ;;
    -a|--arg-with-value)
    arg_with_value="$2"
    shift
    shift
    ;;
    -*)
    echo "Unknown option: $1"
    exit 1
    ;;
    *)
    arg_num=$(( $arg_num + 1 ))
    case $arg_num in
        1)
        first_normal_arg="$1"
        shift
        ;;
        2)
        second_normal_arg="$1"
        shift
        ;;
        *)
        bad_args=TRUE
    esac
    ;;
esac
done

# Handy to have this here when adding arguments to
# see if they're working. Just edit the '0' to be '1'.
if [[ 0 == 1 ]]; then
    echo "first_normal_arg: $first_normal_arg"
    echo "second_normal_arg: $second_normal_arg"
    echo "boolean_arg: $boolean_arg"
    echo "arg_with_value: $arg_with_value"
    exit 0
fi

if [[ $bad_args == TRUE || $arg_num < 2 ]]; then
    echo "Usage: $(basename "$0") <first-normal-arg> <second-normal-arg> [--boolean-arg] [--arg-with-value VALUE]"
    exit 1
fi

未処理の引数を保持するソリューション。デモが含まれています。

これが私の解決策です。非常に柔軟性があり、他とは異なり、外部パッケージを必要とせず、残った引数をきれいに処理します。

使用法は次の./myscript -flag flagvariable -otherflag flagvar2です./myscript -flag flagvariable -otherflag flagvar2

必要なことは、validflags行を編集することだけです。ハイフンを付加し、すべての引数を検索します。次に、次の引数をフラグ名として定義します。

./myscript -flag flagvariable -otherflag flagvar2
echo $flag $otherflag
flagvariable flagvar2

メインコード(短いバージョン、さらに下の例を含む詳細、エラーが発生したバージョン):

#!/usr/bin/env bash
#shebang.io
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
    for flag in $validflags
    do
        sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers

エコーデモが組み込まれた詳細バージョン:

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
echo "all args
[email protected]"
validflags="rate time number"
count=1
for arg in [email protected]
do
    match=0
    argval=$1
#   argval=$(echo [email protected] | cut -d ' ' -f$count)
    for flag in $validflags
    do
            sflag="-"$flag
        if [ "$argval" == "$sflag" ]
        then
            declare $flag=$2
            match=1
        fi
    done
        if [ "$match" == "1" ]
    then
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done

#Cleanup then restore the leftovers
echo "pre final clear args:
[email protected]"
shift $#
echo "post final clear args:
[email protected]"
set -- $leftovers
echo "all post set args:
[email protected]"
echo arg1: $1 arg2: $2

echo leftovers: $leftovers
echo rate $rate time $time number $number

最後に、無効な引数が渡された場合、これはエラーになります。

#!/usr/bin/env bash
#shebang.io
rate=30
time=30
number=30
validflags="rate time number"
count=1
for arg in [email protected]
do
    argval=$1
    match=0
        if [ "${argval:0:1}" == "-" ]
    then
        for flag in $validflags
        do
                sflag="-"$flag
            if [ "$argval" == "$sflag" ]
            then
                declare $flag=$2
                match=1
            fi
        done
        if [ "$match" == "0" ]
        then
            echo "Bad argument: $argval"
            exit 1
        fi
        shift 2
    else
        leftovers=$(echo $leftovers $argval)
        shift
    fi
    count=$(($count+1))
done
#Cleanup then restore the leftovers
shift $#
set -- $leftovers
echo rate $rate time $time number $number
echo leftovers: $leftovers

長所:それが何をするか、それは非常にうまく処理します。これは、他の多くのソリューションが保持していない未使用の引数を保持します。また、スクリプトで手動で定義しなくても、変数を呼び出すことができます。また、対応する引数が指定されていない場合、変数の事前入力も可能です。 (詳細な例を参照)。

短所:単一の複雑な引数文字列を解析できません。たとえば、-xcvfは単一の引数として処理されます。ただし、この機能を追加する追加のコードをマイニングに簡単に書き込むこともできます。

この例では、 getoptevalHEREDOCshiftを使用して、後に続く必須の値の有無にかかわらず、短いパラメーターと長いパラメーターを処理する方法を示します。また、switch / caseステートメントは簡潔でわかりやすいです。

#!/usr/bin/env bash

# usage function
function usage()
{
   cat << HEREDOC

   Usage: $progname [--num NUM] [--time TIME_STR] [--verbose] [--dry-run]

   optional arguments:
     -h, --help           show this help message and exit
     -n, --num NUM        pass in a number
     -t, --time TIME_STR  pass in a time string
     -v, --verbose        increase the verbosity of the bash script
     --dry-run            do a dry run, dont change any files

HEREDOC
}  

# initialize variables
progname=$(basename $0)
verbose=0
dryrun=0
num_str=
time_str=

# use getopt and store the output into $OPTS
# note the use of -o for the short options, --long for the long name options
# and a : for any option that takes a parameter
OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; usage; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  # uncomment the next line to see how shift is working
  # echo "\$1:\"$1\" \$2:\"$2\""
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

if (( $verbose > 0 )); then

   # print out all the parameters we read in
   cat <<-EOM
   num=$num_str
   time=$time_str
   verbose=$verbose
   dryrun=$dryrun
EOM
fi

# The rest of your script below

上記のスクリプトの最も重要な行は次のとおりです。

OPTS=$(getopt -o "hn:t:v" --long "help,num:,time:,verbose,dry-run" -n "$progname" -- "[email protected]")
if [ $? != 0 ] ; then echo "Error in command line arguments." >&2 ; exit 1 ; fi
eval set -- "$OPTS"

while true; do
  case "$1" in
    -h | --help ) usage; exit; ;;
    -n | --num ) num_str="$2"; shift 2 ;;
    -t | --time ) time_str="$2"; shift 2 ;;
    --dry-run ) dryrun=1; shift ;;
    -v | --verbose ) verbose=$((verbose + 1)); shift ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

簡潔に言えば、要点、読みやすく、ほぼすべてを処理します(IMHO)。

それが誰かを助けることを願っています。

私の回答は主にBruno Bronoskyの回答に基づいていますが、私は彼の2つの純粋なbash実装を、私がかなり頻繁に使用するものにまとめました。

# As long as there is at least one more argument, keep looping
while [[ $# -gt 0 ]]; do
    key="$1"
    case "$key" in
        # This is a flag type option. Will catch either -f or --foo
        -f|--foo)
        FOO=1
        ;;
        # Also a flag type option. Will catch either -b or --bar
        -b|--bar)
        BAR=1
        ;;
        # This is an arg value type option. Will catch -o value or --output-file value
        -o|--output-file)
        shift # past the key and to the value
        OUTPUTFILE="$1"
        ;;
        # This is an arg=value type option. Will catch -o=value or --output-file=value
        -o=*|--output-file=*)
        # No need to shift here since the value is part of the same string
        OUTPUTFILE="${key#*=}"
        ;;
        *)
        # Do whatever you want with extra options
        echo "Unknown option '$key'"
        ;;
    esac
    # Shift after checking all the cases to get the next option
    shift
done

これにより、スペースで区切られたオプション/値と、定義された等しい値の両方を持つことができます。

したがって、次のコマンドを使用してスクリプトを実行できます。

./myscript --foo -b -o /fizz/file.txt

と同様:

./myscript -f --bar -o=/fizz/file.txt

両方とも同じ最終結果になるはずです。

長所:

  • -arg = valueと-arg valueの両方が可能

  • bashで使用できる任意の引数名で機能します

    • 意味-aまたは-argまたは--argまたは-argまたは何でも
  • 純粋なバッシュ。 getoptまたはgetoptsを学習/使用する必要はありません

短所:

  • 引数を結合できません

    • no -abcの意味。 -a -b -cを実行する必要があります

これらは私が頭の上で考えることができる唯一の長所/短所です

素敵なbashツールを書くためにbashヘルパーを書いています

プロジェクトのホーム: https : //gitlab.mbedsys.org/mbedsys/bashopts

例:

#!/bin/bash -ei

# load the library
. bashopts.sh

# Enable backtrace dusplay on error
trap 'bashopts_exit_handle' ERR

# Initialize the library
bashopts_setup -n "$0" -d "This is myapp tool description displayed on help message" -s "$HOME/.config/myapprc"

# Declare the options
bashopts_declare -n first_name -l first -o f -d "First name" -t string -i -s -r
bashopts_declare -n last_name -l last -o l -d "Last name" -t string -i -s -r
bashopts_declare -n display_name -l display-name -t string -d "Display name" -e "\$first_name \$last_name"
bashopts_declare -n age -l number -d "Age" -t number
bashopts_declare -n email_list -t string -m add -l email -d "Email adress"

# Parse arguments
bashopts_parse_args "[email protected]"

# Process argument
bashopts_process_args

助けになります:

NAME:
    ./example.sh - This is myapp tool description displayed on help message

USAGE:
    [options and commands] [-- [extra args]]

OPTIONS:
    -h,--help                          Display this help
    -n,--non-interactive true          Non interactive mode - [$bashopts_non_interactive] (type:boolean, default:false)
    -f,--first "John"                  First name - [$first_name] (type:string, default:"")
    -l,--last "Smith"                  Last name - [$last_name] (type:string, default:"")
    --display-name "John Smith"        Display name - [$display_name] (type:string, default:"$first_name $last_name")
    --number 0                         Age - [$age] (type:number, default:0)
    --email                            Email adress - [$email_list] (type:string, default:"")

楽しい :)

これが私のアプローチです-正規表現を使用します。

  • getoptsなし
  • 短いパラメーターのブロックを処理します-qwerty
  • 短いパラメーターを処理します-q -w -e
  • 長いオプションを扱います--qwerty
  • 属性を短いオプションまたは長いオプションに渡すことができます(短いオプションのブロックを使用している場合、属性は最後のオプションにアタッチされます)
  • スペースまたは=を使用して属性を提供できますが、属性はハイフン+スペース「区切り文字」に遭遇するまで一致するため、 --q=qwe ty qwe tyは1つの属性です
  • 上記のすべての組み合わせを処理するため、 -oa -op attr ibute --option=att ribu te --op-tion attribute --option att-ributeが有効です

脚本:

#!/usr/bin/env sh

help_menu() {
  echo "Usage:

  ${0##*/} [-h][-l FILENAME][-d]

Options:

  -h, --help
    display this help and exit

  -l, --logfile=FILENAME
    filename

  -d, --debug
    enable debug
  "
}

parse_options() {
  case $opt in
    h|help)
      help_menu
      exit
     ;;
    l|logfile)
      logfile=${attr}
      ;;
    d|debug)
      debug=true
      ;;
    *)
      echo "Unknown option: ${opt}\nRun ${0##*/} -h for help.">&2
      exit 1
  esac
}
[email protected]

until [ "$options" = "" ]; do
  if [[ $options =~ (^ *(--([a-zA-Z0-9-]+)|-([a-zA-Z0-9-]+))(( |=)(([\_\.\?\/\\a-zA-Z0-9]?[ -]?[\_\.\?a-zA-Z0-9]+)+))?(.*)|(.+)) ]]; then
    if [[ ${BASH_REMATCH[3]} ]]; then # for --option[=][attribute] or --option[=][attribute]
      opt=${BASH_REMATCH[3]}
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    elif [[ ${BASH_REMATCH[4]} ]]; then # for block options -qwert[=][attribute] or single short option -a[=][attribute]
      pile=${BASH_REMATCH[4]}
      while (( ${#pile} > 1 )); do
        opt=${pile:0:1}
        attr=""
        pile=${pile/${pile:0:1}/}
        parse_options
      done
      opt=$pile
      attr=${BASH_REMATCH[7]}
      options=${BASH_REMATCH[9]}
    else # leftovers that don't match
      opt=${BASH_REMATCH[10]}
      options=""
    fi
    parse_options
  fi
done

次のようにtest_args.shという名前のシェルスクリプトを作成するとします。

#!/bin/sh
until [ $# -eq 0 ]
do
  name=${1:1}; shift;
  if [[ -z "$1" || $1 == -* ]] ; then eval "export $name=true"; else eval "export $name=$1"; shift; fi  
done
echo "year=$year month=$month day=$day flag=$flag"

次のコマンドを実行した後:

sh test_args.sh  -year 2017 -flag  -month 12 -day 22 

出力は次のようになります。

year=2017 month=12 day=22 flag=true

プロジェクトを送信したい: https : //github.com/flyingangel/argparser

source argparser.sh
parse_args "[email protected]"

そのような単純な。環境には、引数と同じ名前の変数が入力されます

シンプルで変更が簡単なパラメーターは、任意の順序にすることができます。これは、任意の形式(-a、-a、aなど)のパラメーターを取るように変更できます。

for arg in "[email protected]"
do
   key=$(echo $arg | cut -f1 -d=)`
   value=$(echo $arg | cut -f2 -d=)`
   case "$key" in
        name|-name)      read_name=$value;;
        id|-id)          read_id=$value;;
        *)               echo "I dont know what to do with this"
   ease
done

@ bruno-bronoskyの答えを拡張して、いくつかの一般的なフォーマットを処理する「プリプロセッサー」を追加しました。

  • --longopt=val--longopt val展開し--longopt=val
  • -xyz-x -y -z展開し-xyz
  • サポート--フラグの終わりを示します
  • 予期しないオプションのエラーを表示します
  • コンパクトで読みやすいオプションスイッチ
#!/bin/bash

# Report usage
usage() {
  echo "Usage:"
  echo "$(basename $0) [options] [--] [file1, ...]"

  # Optionally exit with a status code
  if [ -n "$1" ]; then
    exit "$1"
  fi
}

invalid() {
  echo "ERROR: Unrecognized argument: $1" >&2
  usage 1
}

# Pre-process options to:
# - expand -xyz into -x -y -z
# - expand --longopt=arg into --longopt arg
ARGV=()
END_OF_OPT=
while [[ $# -gt 0 ]]; do
  arg="$1"; shift
  case "${END_OF_OPT}${arg}" in
    --) ARGV+=("$arg"); END_OF_OPT=1 ;;
    --*=*)ARGV+=("${arg%%=*}" "${arg#*=}") ;;
    --*) ARGV+=("$arg"); END_OF_OPT=1 ;;
    -*) for i in $(seq 2 ${#arg}); do ARGV+=("-${arg:i-1:1}"); done ;;
    *) ARGV+=("$arg") ;;
  esac
done

# Apply pre-processed options
set -- "${ARGV[@]}"

# Parse options
END_OF_OPT=
POSITIONAL=()
while [[ $# -gt 0 ]]; do
  case "${END_OF_OPT}${1}" in
    -h|--help)      usage 0 ;;
    -p|--password)  shift; PASSWORD="$1" ;;
    -u|--username)  shift; USERNAME="$1" ;;
    -n|--name)      shift; names+=("$1") ;;
    -q|--quiet)     QUIET=1 ;;
    -C|--copy)      COPY=1 ;;
    -N|--notify)    NOTIFY=1 ;;
    --stdin)        READ_STDIN=1 ;;
    --)             END_OF_OPT=1 ;;
    -*)             invalid "$1" ;;
    *)              POSITIONAL+=("$1") ;;
  esac
  shift
done

# Restore positional parameters
set -- "${POSITIONAL[@]}"

以下は、最小限のコードで解析を実現し、サブストリング付きのevalを使用して抽出したいものを定義できるgetoptsです。

基本的にeval "local key='val'"

function myrsync() {

        local backup=("${@}") args=(); while [[ $# -gt 0 ]]; do k="$1";
                case "$k" in
                    ---sourceuser|---sourceurl|---targetuser|---targeturl|---file|---exclude|---include)
                        eval "local ${k:3}='${2}'"; shift; shift    # Past two arguments
                    ;;
                    *)  # Unknown option  
                        args+=("$1"); shift;                        # Past argument only
                    ;;                                              
                esac                                                
        done; set -- "${backup[@]}"                                 # Restore [email protected]


        echo "${sourceurl}"
}

ここでほとんどの答えとして、変数をグローバルではなくローカルとして宣言します。

次のように呼び出されます:

myrsync ---sourceurl http://abc.def.g ---sourceuser myuser ... 

$ {k:3}は基本的に、キーから最初の---を削除する部分文字列です。

コマンドライン引数を解析する方法はいくつかあります(たとえば、GNU getopt(移植不可)とBSD(OSX)getoptとgetopts)-すべて問題があります。このソリューションは

  • ポータブル!
  • 依存関係がなく、bashビルトインにのみ依存しています
  • 短いオプションと長いオプションの両方が可能
  • オプションと引数の間の空白を処理しますが、 =セパレータも使用できます
  • 連結された短いオプションスタイル-vxf
  • オプションの引数を持つオプションを処理します(例を参照)。
  • 同じ機能セットの代替と比較して、コードの膨張を必要としません。すなわち簡潔であり、したがって維持が容易

例:次のいずれか

# flag
-f
--foo

# option with required argument
-b"Hello World"
-b "Hello World"
--bar "Hello World"
--bar="Hello World"

# option with optional argument
--baz
--baz="Optional Hello"

#!/usr/bin/env bash

usage() {
  cat - >&2 <<EOF
NAME
    program-name.sh - Brief description

SYNOPSIS
    program-name.sh [-h|--help]
    program-name.sh [-f|--foo]
                    [-b|--bar <arg>]
                    [--baz[=<arg>]]
                    [--]
                    FILE ...

REQUIRED ARGUMENTS
  FILE ...
          input files

OPTIONS
  -h, --help
          Prints this and exits

  -f, --foo
          A flag option

  -b, --bar <arg>
          Option requiring an argument <arg>

  --baz[=<arg>]
          Option that has an optional argument <arg>. If <arg>
          is not specified, defaults to 'DEFAULT'
  --     
          Specify end of options; useful if the first non option
          argument starts with a hyphen

EOF
}

fatal() {
    for i; do
        echo -e "${i}" >&2
    done
    exit 1
}

# For long option processing
next_arg() {
    if [[ $OPTARG == *=* ]]; then
        # for cases like '--opt=arg'
        OPTARG="${OPTARG#*=}"
    else
        # for cases like '--opt arg'
        OPTARG="${args[$OPTIND]}"
        OPTIND=$((OPTIND + 1))
    fi
}

# ':' means preceding option character expects one argument, except
# first ':' which make getopts run in silent mode. We handle errors with
# wildcard case catch. Long options are considered as the '-' character
optspec=":hfb:-:"
args=("" "[email protected]")  # dummy first element so $1 and $args[1] are aligned
while getopts "$optspec" optchar; do
    case "$optchar" in
        h) usage; exit 0 ;;
        f) foo=1 ;;
        b) bar="$OPTARG" ;;
        -) # long option processing
            case "$OPTARG" in
                help)
                    usage; exit 0 ;;
                foo)
                    foo=1 ;;
                bar|bar=*) next_arg
                    bar="$OPTARG" ;;
                baz)
                    baz=DEFAULT ;;
                baz=*) next_arg
                    baz="$OPTARG" ;;
                -) break ;;
                *) fatal "Unknown option '--${OPTARG}'" "see '${0} --help' for usage" ;;
            esac
            ;;
        *) fatal "Unknown option: '-${OPTARG}'" "See '${0} --help' for usage" ;;
    esac
done

shift $((OPTIND-1))

if [ "$#" -eq 0 ]; then
    fatal "Expected at least one required argument FILE" \
    "See '${0} --help' for usage"
fi

echo "foo=$foo, bar=$bar, baz=$baz, files=${@}"

解析オプション用に作成したものを共有したいと思いました。 私のニーズのいくつか(サブオプション管理、変数引数、オプション引数など)はここの答えでは満たされていないため、これを考え出す必要がありました: https : //github.com/MihirLuthra/bash_option_parser

次のような使用法のfruitというコマンドがあるとします。

fruit ...
   [-e|—-eat|—-chew]
   [-c|--cut <how> <why>]
   <command> [<args>] 

-eは引数を取らない
-cは2つの引数を取ります。つまり、切り方と切り方
<command>は、 appleorangeなどのサブオプション用です( commitpushなどのサブオプションを持つgit似ています)

それを解析するには:

source ./option_parser

parse_options \
    ',' 'OPTIONS' 'ARG_CNT' 'ARGS' 'self' '0' ';' '--'  \
                                                        \
    '-e'    , '—-eat' , '—-chew'  '0'                   \
    '-c'    , '—-cut' ,           '1 1'                 \
    'apple'                       'S'                   \
    'orange'                      'S'                   \
                                                        \
    ';' "[email protected]"

使用法のエラーがあった場合は、 option_parser_error_msgを使用して次のようにoption_parser_error_msgできます。

retval=$?

if [ $retval -ne 0 ]; then
    option_parser_error_msg "$retval" 'OPTIONS'
    exit 1
fi

いくつかのオプションが渡されたかどうかを今すぐ確認するには、

if [ -n "${OPTIONS[-c]}" ]
then
    echo "-c was passed"

    # args can be accessed in a 2D-array-like format
    echo "Arg1 to -c = ${ARGS[-c,0]}"
    echo "Arg2 to -c = ${ARGS[-c,1]}"

fi

サブオプションについては、のようなappleその使用方法次のようになります。

fruit apple ...
   [—-eat-apple|—-chew-apple]
   [-p|—-peel <how>] 

渡されたかどうかを確認し、次のように解析できます。

if [ -n "${OPTION[apple]}" ]
then
    shift_count=${OPTION[apple]}

    parse_options \
    ',' 'OPTIONS_apple' 'ARG_CNT_apple' 'ARGS_apple'    \
    'self' "$shift_count" ';' '--'                      \
                                                        \
    '—-eat-apple' , '—-chew-apple'  '0'                 \
    '-p'          , '—-peel'        '1'                 \
                                                        \
    ';' "[email protected]"

fi

サブオプションの解析は、 $shift_countparse_options渡して行われparse_optionsこれにより、サブオプションの引数に到達するように引数をシフトした後に解析が開始されます。

詳細な説明は、Readmeと例に記載されています。 リポジトリ内

私は@bronsonによる比較的単純な答えに触発され、それを改善しようと試みました(あまり複雑にしないでください)。結果は次のとおりです。

別のシェル引数パーサー(ASAP)– POSIX、 getopt*

  • いずれかを使用し-n [arg]-abn [arg]--name [arg] --name=argオプションのスタイル;
  • 引数は任意の順序で発生する可能性があり、ループの後 [email protected]残されるのは位置的なものだけです。
  • -- 使用して、残りの引数を強制的に定位置として処理します。
  • 無効なオプションと欠落している引数を検出します。
  • getopt(s)または外部ツールに依存しません(1つの機能は単純なsedコマンドを使用します)。
  • ポータブルでコンパクト、非常に読みやすく、 独立した機能を備えています
# Convenience functions.
usage_error () { echo >&2 "$(basename $0):  $1"; exit 2; }
assert_argument () { test "$1" != "$EOL" || usage_error "$2 requires an argument"; }

# One loop, nothing more.
EOL=$(echo '\01\03\03\07')
if [ "$#" != 0 ]; then
  set -- "[email protected]" "$EOL"
  while [ "$1" != "$EOL" ]; do
    opt="$1"; shift
    case "$opt" in

      # Your options go here.
      -f|--flag) flag=true;;
      -n|--name) assert_argument "$1" $opt; name="$1"; shift;;

      -|''|[^-]*) set -- "[email protected]" "$opt";;                                          # positional argument, rotate to the end
      # Extra features (you may remove any line you don't need):
      --*=*)      set -- "${opt%%=*}" "${opt#*=}" "[email protected]";;                        # convert '--name=arg' to '--name' 'arg'
      -[^-]?*)    set -- $(echo "${opt#-}" | sed 's/\(.\)/ -\1/g') "[email protected]";;       # convert '-abc' to '-a' '-b' '-c'
      --)         while [ "$1" != "$EOL" ]; do set -- "[email protected]" "$1"; shift; done;;  # process remaining arguments as positional
      -*)         usage_error "unknown option: '$opt'";;                        # catch misspelled options
      *)          usage_error "this should NEVER happen ($opt)";;               # sanity test for previous patterns
    esac
  done
  shift # $EOL
fi

# Do something cool with "[email protected]"... \o/

注:わかっています... バイナリパターン 0x01030307の引数はロジックを0x01030307可能性があります。しかし、誰かがコマンドラインでそのような引数を渡した場合、それらはそれに値するものです。

コマンドライン引数の解析を容易にするスクリプトを書き留めました-https://github.com/unfor19/bargs

$ bash example.sh -n Willy --gender male -a 99
Name:      Willy
Age:       99
Gender:    male
Location:  chocolate-factory
$ bash example.sh -n Meir --gender male
[ERROR] Required argument: age

Usage: bash example.sh -n Willy --gender male -a 99

--person_name  |  -n  [Willy]              What is your name?
--age          |  -a  [Required]
--gender       |  -g  [Required]
--location     |  -l  [chocolate-factory]  insert your location
$ bash example.sh -h

Usage: bash example.sh -n Willy --gender male -a 99
--person_name  |  -n  [Willy]              What is your name?
--age          |  -a  [Required]
--gender       |  -g  [Required]
--location     |  -l  [chocolate-factory]  insert your location

さらに別のオプションパーサー(およびジェネレーター)を作成しました。

https://github.com/ko1nksm/getoptions

サポートされている構文は、 -a+a-abc-vvv-p value-pvalue--param value--param=value--

移植性があり、POSIXに準拠しており、バシズムはありません。シェルの組み込みコマンドとヘルプのみのcatを使用します。 したがって、ほとんどのOS(Linux、macOS、BSDなど)とすべてのPOSIXシェル(ダッシュ、bash、ksh、zshなど)で動作します。 自動ヘルプ生成が使用可能で、使用方法は非常に簡単です。

#!/bin/sh

. ./getoptions.sh

parser_definition() {
  setup -- "Usage: ${2##*/} [options] [arguments]" ''
  flag    FLAG    -f --flag                      -- "--flag option"
  param   PARAM   -p --param                     -- "--param option"
  option  OPTION  -o --option default:"default"  -- "--option option"
  disp    :usage  -h --help
  disp    VERSION    --version
}

eval "$(getoptions parser_definition parse "$0")"
parse "[email protected]"
eval "set -- $RESTARGS"

echo "FLAG: $FLAG"
echo "PARAM: $PARAM"
echo "OPTION: $OPTION"
printf ': %s\n' "[email protected]" # Rest arguments

eval "$(getoptions_help parser_definition usage "$0")"
usage

Related