Bash에서 명령 줄 인수를 어떻게 구문 분석합니까?


1921

이 줄로 호출되는 스크립트가 있다고 가정 해보십시오.

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

또는 이것 :

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

무엇 각각의 경우에 것을이 같은 구문 분석의 허용 방식 (또는이 둘의 조합)의 $v, $f그리고 $d모든 설정됩니다 true$outFile동일 할 것이다 /fizz/someOtherFile?


1
: zsh을 사용자를 위해 할 수있는 좋은 내장라는 zparseopts있다 zparseopts -D -E -M -- d=debug -debug=d그리고 모두가 -d하고 --debug$debug배열 echo $+debug[1]그 중 하나를 사용하는 경우 0 또는 1을 반환은. 참조 : zsh.org/mla/users/2011/msg00350.html
dezza

1
정말 좋은 튜토리얼 : linuxcommand.org/lc3_wss0120.php . 특히 "명령 줄 옵션"예제를 좋아합니다.
Gabriel Staples

답변:


2675

방법 # 1 : getopt [s]없이 bash 사용

키-값 쌍 인수를 전달하는 두 가지 일반적인 방법은 다음과 같습니다.

Bash Space-Separated (예 --option argument:) (getopt없이)

용법 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 (예 --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 "$@"
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#*=} 에서 "하위 문자열 제거"에 대한 검색 을보다 잘 이해하려면 . 그것과 동등한 기능을 하는 불필요한 서브 프로세스를 호출 또는 호출하는 불필요한 서브 프로세스.`sed 's/[^=]*=//' <<< "$i"``echo "$i" | sed 's/[^=]*=//'`

방법 # 2 : getopt와 함께 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: $@"
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. -vf filename일반적인 Unix 방식 과 같은 여러 단일 옵션을 자동으로 처리 할 수 ​​있습니다 .

의 단점은 getopts단지 짧은 옵션 (처리 할 수있다 -h, 없다 --help추가 코드없이)를.

모든 구문과 변수의 의미를 설명 하는 getopts 학습서 가 있습니다. bash에는 또한 help getopts유익한 정보가 있습니다.


44
이것이 사실입니까? Wikipedia 에 따르면 최신 GNU 고급 버전이 getopt있으며 그중 getopts일부 기능이 모두 포함되어 있습니다. man getoptUbuntu 13.04 getopt - parse command options (enhanced)에서 이름으로 출력 되므로이 향상된 버전이 표준이라고 가정합니다.
Livven

47
시스템에서 어떤 특정한 방법이 있다는 것은 "표준화"라는 가정에 기반을 둔 매우 약한 전제입니다.
szablica

13
@Livven getopt은 GNU 유틸리티가 아니며의 일부입니다 util-linux.
Stephane Chazelas

4
를 사용하는 경우을 -gt 0제거한 다음 shiftesac모두 shift1 *) break;;씩 늘리고이 경우를 추가하십시오 . 비 선택적 인수를 처리 할 수 ​​있습니다. 예 : pastebin.com/6DJ57HTc
Nicolas Lacombe

2
반향하지 않습니다 –default. 첫 번째 예 –default에서 마지막 인수 인 경우while [[ $# -gt 1 ]]while [[ $# -gt 0 ]]
kolydart

562

향상된 getopt에 대한 답변은 없습니다 . 그리고 최고 투표 답변 은 오해의 소지가 있습니다 : 그것은 -⁠vfd스타일 짧은 옵션 (OP에 의해 요청 된) 또는 위치 인수 후의 옵션 (OP에 의해 요청 된)을 무시합니다 . 파싱 ​​오류를 무시합니다. 대신 :

  • getoptutil-linux 또는 이전 GNU glibc에서 향상된 기능 을 사용하십시오 . 1
  • getopt_long()GNU glibc의 C 기능 과 함께 작동합니다 .
  • 유용한 구별 기능 이 모두 있습니다 (다른 기능은 없음).
    • 인수 2 에서 공백, 인용 문자 및 심지어 2 진을 처리합니다 (향상 getopt되지 않음)
    • 마지막에 옵션을 처리 할 수 ​​있습니다 : script.sh -o outFile file1 file2 -v( getopts하지 않습니다)
    • 수 있습니다 =스타일의 긴 옵션 : script.sh --outfile=fileOut --infile fileIn(모두를 허용하는 것은 자기 분석하면 긴하다)
    • 결합 된 짧은 옵션 허용 -vfd( 예 : 자체 구문 분석의 경우 실제 작업)
    • 옵션 인수를 터치 할 수 있습니다. 예 : -oOutfile또는-vfdoOutfile
  • 아직 나이가 3 (리눅스가있다 예를 들어)에는 GNU 시스템이 누락되지 않도록.
  • 다음과 같이 존재 여부를 테스트 할 수 있습니다. 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   -- "$@"   to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
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"

Cygwin을 포함한 대부분의 "bash-systems"에서 1 개의 향상된 getopt를 사용할 수 있습니다. OS X에서 brew install gnu-getopt 또는sudo port install getopt
2를 시도하십시오. POSIXexec()규칙은 명령 행 인수에서 2 진 NULL을 전달하는 확실한 방법이 없습니다. 그 바이트는 1997 년 또는 그 이전에 발표 된인수
3 첫 번째 버전을조기에 종료합니다(1997 년까지만 추적했습니다)


4
고마워 긴 옵션에 대한 지원이 필요하고 Solaris를 사용하지 않는 경우 기능 방법으로 en.wikipedia.org/wiki/Getopts 의 기능 표에서 확인했습니다 getopt.
johncip

4
래퍼 스크립트와 관련된 옵션이 거의 getopt없을 수 있는 래퍼 스크립트에서 편리하게 사용할 수 없으며 래퍼가 아닌 스크립트 옵션을 래핑 된 실행 파일에 그대로 전달 하는 것이 유일한 경고입니다 . 하자 내가 가지고 있다고 grep래퍼라고 mygrep나는 옵션이 --foo특정을 mygrep다음 내가 할 수없는, mygrep --foo -A 2그리고이 -A 2에 자동으로 전달을 grep; 내가 필요로mygrep --foo -- -A 2. 다음은 솔루션 위에 구현 한 것입니다.
Kaushal Modi

2
@bobpaul util-linux에 대한 귀하의 진술은 잘못되어 오도됩니다 : 패키지는 Ubuntu / Debian에서“필수”로 표시되어 있습니다. 따라서 항상 설치됩니다. – 어떤 배포판에 대해 이야기하고 있습니까 (의도적으로 설치해야한다고 말하는 곳)?
Robert Siemer

3
Mac에서는 적어도 현재 10.14.3까지는 작동하지 않습니다. 배송 된 getopt는 1999 년부터 BSD getopt입니다 ...
jjj

2
@transang 반환 값의 부울 부정. 그리고 부작용 : 명령이 실패하도록 허용하십시오 (그렇지 않으면 errexit는 오류로 인해 프로그램을 중단시킵니다). -스크립트의 주석이 더 많은 것을 알려줍니다. 그렇지 않은 경우 :man bash
Robert Siemer

144

간결한 방법

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

3
이것이 내가하고있는 일입니다. 해야 while [[ "$#" > 1 ]]내가 부울 플래그 라인을 종료 지원하려는 경우 ./script.sh --debug dev --uglify fast --verbose. 예 : gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

12
와! 간단하고 깨끗합니다! 이것이 내가 이것을 사용하는 방법 : gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
hfossli

2
소스를 다루거나 사용자의 기능이 실제로 어디서 시작되는지 궁금해하는 것보다 각 스크립트에 붙여 넣는 것이 훨씬 좋습니다.
RealHandy

경고 : 중복 된 인수를 허용하며 최신 인수가 우선합니다. 예를 들면 ./script.sh -d dev -d prod초래 deploy == 'prod'. 어쨌든 그것을 사용 : P :) : +1 :
yair

나는 이것을 사용하고 있습니다 (감사합니다!)하지만 빈 인수 값을 허용합니다. 예를 들어 ./script.sh -d오류를 생성하지 않지만 $deploy빈 문자열로 설정하십시오 .
EM0

137

에서 : 약간 수정 한 digitalpeer.com

용법 myscript.sh -p=my_prefix -s=dirname -l=libname

#!/bin/bash
for i in "$@"
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#*=} 에서 "하위 문자열 제거"에 대한 검색 을보다 잘 이해하려면 . 그것과 동등한 기능을 하는 불필요한 서브 프로세스를 호출 또는 호출하는 불필요한 서브 프로세스.`sed 's/[^=]*=//' <<< "$i"``echo "$i" | sed 's/[^=]*=//'`


4
산뜻한! 공백으로 구분 된 인수 a la에는 작동하지 않습니다 mount -t tempfs .... 하나는 아마 같은 것을 통해이 문제를 해결할 수 while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;
토비아스 Kienzler

3
-vfd스타일 결합 짧은 옵션을 처리 할 수 ​​없습니다 .
Robert Siemer

105

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
-->--

우리는 두 개의 인수로 시작하고 "getopt"는 옵션을 분리하고 각각의 인수에 넣습니다. "-"도 추가되었습니다.


4
$*사용법이 잘못되었습니다 getopt. (공백이있는 인수를 호스로 묶습니다.) 올바른 사용법 은 내 대답 을 참조하십시오 .
Robert Siemer

왜 더 복잡하게 만들고 싶습니까?
SDsolar

스크립트의 첫 번째 부분 인 @Matt J (i의 경우)는 $ i 대신 "$ i"를 사용하는 경우 공백이있는 인수를 처리 할 수 ​​있습니다. getopts는 공백이있는 인수를 처리 할 수없는 것 같습니다. for i 루프보다 getopt를 사용하면 어떤 이점이 있습니까?
thebunnyrules

99

무시할 다른 예를 추가 할 위험이 있으므로 여기 내 계획이 있습니다.

  • 손잡이 -n arg--name=arg
  • 끝에 인수를 허용
  • 맞춤법이 틀린 경우 제정신 오류 표시
  • 호환 가능, bashism을 사용하지 않음
  • 읽을 수 있고 루프에서 상태를 유지할 필요가 없습니다.

누군가에게 유용하기를 바랍니다.

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

4
지연 돼서 죄송합니다. 내 스크립트에서 handle_argument 함수는 모든 비 옵션 인수를받습니다. 해당 줄을 원하는대로 바꾸 *) die "unrecognized argument: $1"거나 인수를 변수로 수집 할 수 *) args+="$1"; shift 1;;있습니다.
bronson

놀랄 만한! 나는 몇 가지 답변을 테스트했지만 이것은 많은 위치 매개 변수 (전후 플래그)를 포함하여 모든 경우에 효과가있는 유일한 것입니다.
Guilherme Garnier

2
간결한 코드이지만 -n을 사용하고 다른 인수를 사용하지 않으면에 오류로 인해 무한 루프가 발생하여 대신 두 번 shift 2발행 shift됩니다 shift 2. 수정 제안.
lauksas

42

나는이 질문에 4 년 늦었지만 포기하고 싶습니다. 이전 답변을 이전의 임시 매개 변수 구문 분석을 정리하기위한 출발점으로 사용했습니다. 그런 다음 다음 템플릿 코드를 리팩터링했습니다. = 또는 공백으로 구분 된 인수와 함께 여러 개의 짧은 매개 변수를 함께 사용하여 long 및 short 매개 변수를 처리합니다. 마지막으로 매개 변수가 아닌 인수를 $ 1, $ 2 .. 변수에 다시 삽입합니다. 도움이 되길 바랍니다.

#!/usr/bin/env bash

# NOTICE: Uncomment if your script depends on bashisms.
#if [ -z "$BASH_VERSION" ]; then bash $0 $@ ; 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

이 코드는 다음과 같은 인수로 옵션을 처리 할 수 ​​없습니다 -c1. 그리고 사용 =자신의 주장에서 짧은 옵션을 구분하기는 ... 특이
로버트 Siemer

2
이 유용한 코드 덩어리에 대해 두 가지 문제가 발생했습니다. 1) "-c = foo"의 경우 "shift"는 다음 매개 변수를 먹습니다. 2) 결합 가능한 짧은 옵션을 위해 'c'를 "[cfr]"패턴에 포함해서는 안됩니다.
sfnd

36

필자는 스크립트에서 휴대용 파싱을 작성 해야하는 문제를 발견하여 Argbash- 스크립트의 인수 파싱 코드를 생성 할 수있는 FOSS 코드 생성기를 작성했습니다.

https://argbash.io


argbash를 작성해 주셔서 감사합니다. 방금 사용하여 잘 작동했습니다. OS X 10.11 El Capitan에있는 오래된 bash 3.x를 지원하는 코드 생성기이기 때문에 주로 argbash를 사용했습니다. 유일한 단점은 코드 생성기 접근 방식이 모듈 호출과 비교하여 기본 스크립트에서 많은 코드를 의미한다는 것입니다.
RichVel

실제로 스크립트에 포함하거나 별도의 파일로 가져 와서 소스를 제공 할 수 있도록 맞춤형 구문 분석 라이브러리를 생성하는 방식으로 Argbash를 사용할 수 있습니다. 이를 설명하기 위해 예제 를 추가 했으며 설명서에서도 더 명확하게 설명했습니다.
bubla

알아 둘만 한. 이 예제는 흥미롭지 만 여전히 명확하지는 않습니다. 생성 된 스크립트의 이름을 'parse_lib.sh'또는 이와 유사하게 변경하고 주 스크립트가 호출하는 위치를 표시 할 수 있습니다 (보다 복잡한 사용 사례 인 랩핑 스크립트 섹션에서와 같이).
RichVel

이 문제는 최신 버전의 argbash에서 해결되었습니다. 설명서가 개선되었으며 빠른 시작 argbash-init 스크립트가 도입되었으며 argbash.io/generate에서 온라인으로 argbash를
bubla

29

내 대답은 주로 Bruno Bronosky의 답변을 기반으로 하지만 두 가지 순수한 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 값을 모두 허용합니다

  • bash에서 사용할 수있는 모든 arg 이름과 함께 작동합니다.

    • 의미 -a 또는 -arg 또는 --arg 또는 -arg 또는 무엇이든
  • 순수한 배쉬. getopt 또는 getopts를 배우거나 사용할 필요가 없습니다.

단점 :

  • 인수를 결합 할 수 없습니다

    • 의미 없음 -abc. -a -b -c를 ​​수행해야합니다.

이것들은 내가 머리 꼭대기에서 생각할 수있는 유일한 장단점입니다.


15

나는 이것이 사용하기에 충분히 간단하다고 생각한다.

#!/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

1
나는 모든 것을 읽었고 이것은 내가 선호하는 것입니다. -a=1argc 스타일 로 사용 하고 싶지 않습니다 . 우선 기본 옵션 옵션을, 나중에 단일 간격을 가진 특수 옵션을 선호합니다 -o option. 나는 argvs를 읽는 가장 간단한 vs 더 나은 방법을 찾고 있습니다.
m3nda

실제로 잘 작동하지만 인수가 아닌 옵션에 인수를 전달하면 다음 옵션이 모두 인수로 사용됩니다. 이 줄 ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile은 자신의 스크립트로 확인할 수 있습니다 . -d 옵션이 d로 설정되지 않았습니다 :
m3nda

15

@guneysus의 탁월한 답변을 확장하여 다음과 같이 사용자가 원하는 구문을 사용할 수 있도록 조정합니다.

command -x=myfilename.ext --another_switch 

vs

command -x myfilename.ext --another_switch

즉, 등호를 공백으로 바꿀 수 있습니다.

이 "퍼지 해석"은 마음에 들지 않을 수도 있지만 다른 유틸리티와 호환 가능한 스크립트를 만들 경우 (ffmpeg와 함께 작동해야하는 내 경우와 같이) 유연성이 유용합니다.

STD_IN=0

prefix=""
key=""
value=""
for keyValue in "$@"
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

13

이 예제 프로그램은 사용 방법 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" -- "$@")
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" -- "$@")
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)을 처리합니다.

누군가에게 도움이되기를 바랍니다.


1
이것은 최고의 답변 중 하나입니다.
Mr. Polywhirl

11

parse_params명령 줄에서 매개 변수를 구문 분석 하는 기능 을 제공합니다 .

  1. 추가 유틸리티가없는 순수한 Bash 솔루션입니다.
  2. 전역 범위를 오염시키지 않습니다.
  3. 더 쉽게 논리를 구축 할 수 있도록 변수를 사용하기 간단하게 반환합니다.
  4. PARAMS 전에 대시의 양이 중요하지 않습니다 ( --all동일 -all같음 all=all)

아래 스크립트는 복사-붙여 넣기 작업 데모입니다. show_use사용법을 이해하려면 기능을 참조하십시오 parse_params.

한계 :

  1. 공백으로 구분 된 매개 변수 ( -d 1)를 지원하지 않습니다
  2. 파람 이름은 그래서 대시를 잃게됩니다 --any-param-anyparam동일
  3. eval $(parse_params "$@")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 "$@")
    # --
    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

당신은 그냥 할 당신의 bash는 스크립트에 와서 구문 분석 PARAMS에 데모 사용show_use "$@"
Oleksii Chekulaiev

기본적으로 github.com/renatosilva/easyoptions 는 동일한 방식으로 동일하지만이 기능보다 약간 더 큽니다.
Oleksii Chekulaiev

10

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

예제 스크립트 상단의 주석이 구문 분석되어 기본 사용법 도움말 문자열과 인수 스펙을 제공한다는 것을 깨닫는 데 잠시 시간이 걸렸습니다. 이것은 훌륭한 해결책이며 2 년 안에 6 표만 받았습니다. 아마도이 질문은 사람들이 알기에는 너무 늪입니다.
변성

어떤 의미에서 귀하의 솔루션은 훨씬 우수합니다 ( "표준"옵션 구문을 지원하지 않는 @ OleksiiChekulaiev 's 제외). 솔루션에서 스크립트 작성기가 각 옵션의 이름을 한 번만 지정하면되기 때문 입니다. 다른 솔루션은 사용법, '사례'패턴 및 변수 설정에서 3 번 지정해야한다는 사실이 계속 귀찮았습니다. getopt 조차도이 문제가 있습니다. 그러나 코드가 내 컴퓨터에서 느리다-Bash 구현의 경우 0.11, Ruby의 경우 0.28입니다. 명시적인 "대소 문자 구분"구문 분석의 경우 0.02입니다.
변성

C로 작성된 더 빠른 버전을 원합니다. 또한 zsh와 호환되는 버전입니다. 어쩌면 이것은 별도의 질문이 필요합니다 ( "표준 긴 옵션 구문을 허용하고 옵션 이름을 두 번 이상 입력하지 않아도되는 Bash와 같은 쉘에서 명령 행 인수를 구문 분석하는 방법이 있습니까?").
변성

10

getopts는 # 1을 설치했고 # 2를 동일한 플랫폼에서 실행하려는 경우 효과적입니다. 예를 들어 OSX와 Linux는 이와 관련하여 다르게 작동합니다.

다음은 같거나 같지 않고 부울 플래그를 지원하는 (getopts가 아닌) 솔루션입니다. 예를 들어 다음과 같은 방법으로 스크립트를 실행할 수 있습니다.

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

# parse the arguments.
COUNTER=0
ARGS=("$@")
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

8

이것은 스택에서 어딘가에 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
...
}

8

@ bruno-bronosky의 답변을 확장하여 일반적인 형식을 처리하기 위해 "전 처리기"를 추가했습니다.

  • 을 확장 --longopt=val--longopt val
  • 을 확장 -xyz-x -y -z
  • --플래그의 끝을 나타내는 지원
  • 예기치 않은 옵션에 대한 오류를 표시합니다
  • 작고 읽기 쉬운 옵션 스위치
#!/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[@]}"

6

cmdline 인수를 구문 분석하는 방법에는 여러 가지가 있습니다 (예 : GNU getopt (휴대용 아님) vs BSD (OSX) getopt vs 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=("" "$@")  # 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=${@}"

5

옵션 파싱 버전을 제공하고 싶습니다.

-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

1
$ {key + x}에서 "+ x"의 의미는 무엇입니까?
Luca Davanzo

1
'키'가 있는지 확인하는 테스트입니다. 더 나아가서 키를 설정 해제하면 내부 while 루프가 끊어집니다.
galmok

5

처리되지 않은 인수를 유지하는 솔루션. 데모 포함

여기 내 해결책이 있습니다. 매우 유연하며 다른 패키지와 달리 외부 패키지가 필요하지 않으며 남은 인수를 깔끔하게 처리합니다.

사용법은 : ./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 $@
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
$@"
validflags="rate time number"
count=1
for arg in $@
do
    match=0
    argval=$1
#   argval=$(echo $@ | 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:
$@"
shift $#
echo "post final clear args:
$@"
set -- $leftovers
echo "all post set args:
$@"
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 $@
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는 단일 인수로 처리됩니다. 이 기능을 추가하는 추가 코드를 광산에 다소 쉽게 작성할 수 있습니다.



3

참고 getopt(1)AT & T에서 짧은 생활 실수였다.

getopt는 1984 년에 만들어졌지만 실제로 사용할 수 없었기 때문에 1986 년에 이미 묻혔습니다.

getopt매우 구식 이라는 사실에 대한 증거 는 1986 년에 Bourne Shell에 추가 된 getopt(1)매뉴얼 페이지 "$*"대신 "$@".getopts(1) 쉘 내장 내부 공간에 대한 인수를 처리하기 위해 .

BTW : 쉘 스크립트에서 긴 옵션을 구문 분석하는 데 관심이 있다면 getopt(3)libc (Solaris) 의 구현과 ksh93짧은 옵션의 별명으로 긴 옵션을 지원하는 균일 한 긴 옵션 구현을 추가 한 것이 흥미로울 수 있습니다. 이 발생 ksh93하고,이 Bourne Shell를 통해 긴 옵션에 대한 균일 한 인터페이스를 구현 getopts.

Bourne Shell 매뉴얼 페이지에서 가져온 긴 옵션의 예 :

getopts "f:(file)(input-file)o:(output-file)" OPTX "$@"

Bourne Shell과 ksh93에서 옵션 별명을 사용할 수있는 기간을 보여줍니다.

최근 Bourne Shell의 매뉴얼 페이지를 참조하십시오.

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

OpenSolaris의 getopt (3) 매뉴얼 페이지 :

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

마지막으로 getopt (1) 매뉴얼 페이지에서 오래된 $ *를 확인하십시오.

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


3

멋진 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 "$@"

# 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:"")

즐겨 :)


나는 Mac OS X에서 이것을 얻는다 :```lib / bashopts.sh : line 138 : 선언 : -A : 유효하지 않은 옵션 선언 : 사용법 : 선언 [-afFirtx] [-p] [name [= value] ...] lib / bashopts.sh에서 오류 : 138. 'declare -x -A bashopts_optprop_name'이 상태 2로 종료 됨 호출 트리 : 1 : lib / controller.sh : 4 source (...) 상태 1로 종료```
Josh Wulf

이것을 사용하려면 Bash 버전 4가 필요합니다. Mac에서 기본 버전은 3입니다. 홈 브루를 사용하여 bash 4를 설치할 수 있습니다.
Josh Wulf

3

내 접근 방식은 다음과 같습니다-regexp를 사용하십시오.

  • getopts 없음
  • 짧은 매개 변수 블록을 처리합니다. -qwerty
  • 짧은 매개 변수를 처리합니다. -q -w -e
  • 긴 옵션을 처리합니다 --qwerty
  • 짧은 옵션 또는 긴 옵션에 속성을 전달할 수 있습니다 (짧은 옵션 블록을 사용하는 경우 속성이 마지막 옵션에 첨부 됨)
  • 공백을 사용하거나 =속성을 제공 할 수 있지만 하이픈 + 공백 "구분 기호"가 나타날 때까지 속성이 일치하므로 --q=qwe ty qwe ty하나의 속성
  • 그것은 위의 모든 믹스를 처리하므로 -o a -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
}
options=$@

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

이 같은. 어쩌면 -e 매개 변수를 추가하여 새 줄과 에코 할 수도 있습니다.
mauron85

3

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

5
이것은 노아의 대답 과 같은 접근 방식을 취하지 만 안전 점검 / 보호 수단은 적습니다. 이를 통해 스크립트 환경에 임의의 인수를 쓸 수 있으며 여기서 eval을 사용하면 명령 삽입이 허용 될 것입니다.
Will Barnwell


2

위치 및 플래그 기반 인수 혼합

--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

2

다음은 최소한의 코드로 구문 분석을 수행하고 하위 문자열과 함께 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 $@


        echo "${sourceurl}"
}

여기에서 대부분의 답변으로 변수를 전역 대신 지역 변수로 선언합니다.

호출 :

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

$ {k : 3}은 기본적으로 ---키 에서 첫 번째를 제거하는 하위 문자열 입니다.


1

또한 값을 설정할 수 있으며 누군가 입력을 제공하는 경우 해당 값으로 기본값을 대체합니다.

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}"

1

getopt [s], POSIX, 이전 Unix 스타일이없는 다른 솔루션

Bruno Bronosky가 여기에 게시 한 솔루션과 유사하지만 사용법이없는 솔루션 입니다 getopt(s).

내 솔루션의 기능을 차별화 홈페이지는 옵션처럼 함께 연결된 가질 수 있다는 것입니다 tar -xzf foo.tar.gzIS가 동일 tar -x -z -f foo.tar.gz. 그리고 단지처럼 tar, ps등 주요 하이픈 짧은 옵션의 블록 선택 사항입니다 (하지만이 쉽게 변경 될 수 있습니다). 긴 옵션도 지원됩니다 (하지만 블록이 1 개로 시작하면 2 개의 하이픈이 필요합니다).

예제 옵션이있는 코드

#!/bin/sh

echo
echo "POSIX-compliant getopt(s)-free old-style-supporting option parser from phk@[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는 않습니다) (이후의 예 참조).

인수가있는 여러 옵션

다른 보너스로서, 옵션 파라미터는 필요한 옵션을 갖는 파라미터에 의해 옵션의 순서대로 소비된다. 명령 줄 abc X Y Z(또는 -abc X Y Z)을 사용하여 스크립트 출력을 확인하십시오 .

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

긴 옵션도 연결

또한 블록에서 마지막에 발생하는 옵션 블록에 긴 옵션을 사용할 수도 있습니다. 따라서 다음 명령 행은 옵션과 인수가 처리되는 순서를 포함하여 모두 동일합니다.

  • -cba Z Y X
  • cba Z Y X
  • -cb-aaa-0-args Z Y X
  • -c-bbb-1-args Z Y X -a
  • --ccc-2-args Z Y -ba X
  • c Z Y b X a
  • -c Z Y -b X -a
  • --ccc-2-args Z Y --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...

이 솔루션에는 없습니다

선택적 인수

선택적인 인수가있는 옵션은 약간의 작업으로 가능해야합니다 (예 : 하이픈이없는 블록이 있는지 예상). 그런 다음 사용자는 선택적 매개 변수가있는 매개 변수가있는 블록 뒤에 오는 모든 블록 앞에 하이픈을 넣어야합니다. 어쩌면 이것은 사용자와 통신하기에 너무 복잡하기 때문에이 경우에는 하이픈이 필요합니다.

여러 가능한 매개 변수로 상황이 더욱 복잡해집니다. 나는 인수가 그에 대한 것인지 아닌지를 결정함으로써 옵션을 현명하게 만들지 말 것을 권고 할 것이다.

나는 개인적으로 선택적 인수 대신 추가 옵션을 선호합니다.

등호가있는 옵션 인수

선택적 인수와 마찬가지로 나는 이것의 팬이 아닙니다 (BTW, 다른 매개 변수 스타일의 장단점을 논의하기위한 스레드가 있습니까?)하지만 이것을 원한다면 http : // 에서와 같이 직접 구현할 수 있습니다 mywiki.wooledge.org/BashFAQ/035#Manual_loop--long-with-arg=?*case 문을 작성하고 등호 제거하기 (이것은 약간의 노력으로 매개 변수 연결이 가능하지만 독자를위한 연습으로 남겨 두었다고 말하는 사이트 BTW입니다) "그들이 나를 그들의 말로 데려 갔지만 처음부터 시작했습니다.)

기타 노트

POSIX 호환, (예를 들어, 심지어 내가 처리했다 고대 비지 박스의 설정에서 작동 cut, headgetopts실종).

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.