getopts를 사용하여 길고 짧은 명령 행 옵션 처리


410

쉘 스크립트를 사용하여 길고 짧은 형태의 명령 행 옵션을 호출하고 싶습니다.

나는 그것이 getopts사용될 수 있다는 것을 알고 있지만 Perl과 마찬가지로 쉘로도 똑같이 할 수 없었습니다.

이 작업을 수행하는 방법에 대한 아이디어는 다음과 같은 옵션을 사용할 수 있습니다.

./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/

위의 두 명령 모두 내 쉘과 동일한 것을 의미하지만을 사용 getopts하면이를 구현할 수 없습니까?


2
IMHO, 허용되는 답변이 최선의 답변이 아닙니다. @Arvid Requate가 시연 한 것처럼 수행 할 수있는 "-"및 "-"인수를 모두 처리하기 위해 getopts를 사용하는 방법을 보여주지 않습니다. 비슷한 개념을 사용하여 다른 대답을 삽입하고 있지만 필요한 인수 값을 삽입하는 "잊어 버린"사용자 오류를 처리합니다. 요점 : getopts를 작동시킬 수 있습니다. 크로스 플랫폼 이식성이 필요한 경우 "getopt"를 사용하지 않아야합니다. 또한 getopts는 쉘에 대한 POSIX 표준의 일부이므로 이식성이 뛰어납니다.
pauljohn32

답변:


304

고려할 수있는 세 가지 구현이 있습니다.

  • 배쉬 내장 getopts. 이중 대시 접두사가있는 긴 옵션 이름은 지원하지 않습니다. 단일 문자 옵션 만 지원합니다.

  • 독립형 getopt명령 의 BSD UNIX 구현 (MacOS가 사용하는 것). 긴 옵션도 지원하지 않습니다.

  • 독립형 GNU 구현 getopt. GNU getopt(3)( getopt(1)Linux 의 명령 행 에서 사용)는 긴 옵션 구문 분석을 지원합니다.


다른 답변 getopts은 긴 옵션을 모방하기 위해 bash 내장 을 사용하는 솔루션을 보여줍니다 . 이 솔루션은 실제로 문자가 "-"인 짧은 옵션을 만듭니다. 따라서 플래그로 "-"를 얻습니다. 그런 다음 그 뒤에 OPTARG가되고 중첩으로 OPTARG를 테스트합니다 case.

이것은 영리하지만주의 사항이 있습니다.

  • getoptsopt 사양을 시행 할 수 없습니다. 사용자가 잘못된 옵션을 제공하면 오류를 반환 할 수 없습니다. OPTARG를 구문 분석 할 때 고유 한 오류 점검을 수행해야합니다.
  • OPTARG는 긴 옵션 이름에 사용되므로 긴 옵션 자체에 인수가있는 경우 사용법이 복잡해집니다. 추가 사례로 직접 코딩해야합니다.

따라서 긴 옵션에 대한 지원 부족을 해결하기 위해 더 많은 코드를 작성할 수는 있지만 훨씬 더 많은 작업이며 getopt 파서를 사용하여 코드를 단순화하는 목적을 부분적으로 무효화합니다.


18
그래서. 크로스 플랫폼, 휴대용 솔루션이란 무엇입니까?
troelskn

6
GNU Getopt가 유일한 선택 인 것 같습니다. Mac에서는 macports에서 GNU getopt를 설치하십시오. Windows에서는 Cygwin과 함께 GNU getopt를 설치합니다.
Bill Karwin

2
분명히 ksh getopts 긴 옵션을 처리 할 수 있습니다.
Tgr

1
@Bill +1이지만 Mac의 소스 ( software.frodo.looijaard.name/getopt )에서 getopt를 빌드하는 것도 상당히 간단합니다 . "getopt -T; echo $?"를 사용하여 스크립트 내에서 시스템에 설치된 getopt 버전을 확인할 수도 있습니다.
Chinasaur

8
@Bill Karwin : "bash getopts 내장은 이중 대시 접두어로 긴 옵션 이름을 지원하지 않습니다." 그러나 getopts는 긴 옵션을 지원하도록 만들 수 있습니다 . 아래 stackoverflow.com/a/7680682/915044를 참조하십시오 .
TomRoche

305

getopt그리고 getopts다른 짐승이 있고, 사람들은 무슨 오해의 조금을 갖고있는 것 같다. getoptsbash명령 행 옵션을 루프로 처리하고 찾은 각 옵션과 값을 내장 변수에 차례로 지정하여 추가로 처리 할 수있는 내장 명령입니다. getopt그러나 외부 유틸리티 프로그램이며 bash , Perl 모듈 또는 Python / 모듈 과 같은 옵션을 실제로 처리하지는 않습니다 . 이 모든 수행은 규범화에게 전달되는 옵션입니다 - 즉, 그것이 그들을 처리하는 쉘 스크립트 쉽게 그래서, 더 표준 형태로 변환. 예를 들어의 응용 프로그램은 다음을 변환 할 수 있습니다.getoptsGetoptoptparseargparsegetoptgetopt

myscript -ab infile.txt -ooutfile.txt

이것으로 :

myscript -a -b -o outfile.txt infile.txt

실제 처리를 직접 수행해야합니다. getopt옵션을 지정할 수있는 방법에 대해 다양한 제한을 설정 한 경우 전혀 사용할 필요가 없습니다 .

  • 인수 당 하나의 옵션 만 넣으십시오.
  • 모든 옵션은 위치 매개 변수 (즉, 옵션이 아닌 인수)보다 우선합니다.
  • 값이있는 옵션 (예 : -o위)의 경우 값은 공백 뒤에 별도의 인수로 가야합니다.

getopt대신에 사용 getopts합니까? 기본적인 이유는 GNU만이 getopt긴 이름의 명령 줄 옵션을 지원하기 때문입니다 . 1 ( getoptLinux에서는 GNU 가 기본값입니다. Mac OS X 및 FreeBSD는 기본적이고 유용 getopt하지는 않지만 GNU 버전을 설치할 수 있습니다 (아래 참조).

예를 들어, 다음 getopt은 내 스크립트에서 GNU를 사용하는 예입니다 javawrap.

# NOTE: This requires GNU getopt.  On Mac OS X and FreeBSD, you have to install this
# separately; see below.
TEMP=`getopt -o vdm: --long verbose,debug,memory:,debugfile:,minheap:,maxheap: \
             -n 'javawrap' -- "$@"`

if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

VERBOSE=false
DEBUG=false
MEMORY=
DEBUGFILE=
JAVA_MISC_OPT=
while true; do
  case "$1" in
    -v | --verbose ) VERBOSE=true; shift ;;
    -d | --debug ) DEBUG=true; shift ;;
    -m | --memory ) MEMORY="$2"; shift 2 ;;
    --debugfile ) DEBUGFILE="$2"; shift 2 ;;
    --minheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MinHeapFreeRatio=$2"; shift 2 ;;
    --maxheap )
      JAVA_MISC_OPT="$JAVA_MISC_OPT -XX:MaxHeapFreeRatio=$2"; shift 2 ;;
    -- ) shift; break ;;
    * ) break ;;
  esac
done

이를 통해 유사 --verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"하거나 유사한 옵션을 지정할 수 있습니다 . 호출의 효과는 getopt옵션을 --verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"보다 쉽게 ​​처리 할 수 ​​있도록 옵션을 정규화하는 것입니다. 주위에 인용 "$1"하고 "$2"그들에 공백이 제대로 처리 얻을과 그 인수를 보장하므로 중요하다.

처음 9 줄을 삭제하면 (모든 eval set줄 을 통해 ) 코드가 계속 작동합니다 ! 그러나 코드는 어떤 종류의 옵션을 받아 들일지 결정하기가 훨씬 쉽습니다. 특히, 위에서 설명한 "정식"형식으로 모든 옵션을 지정해야합니다. 의 사용으로 getopt, 그러나, 당신은 그룹의 단일 문자 옵션은 긴 옵션을 사용하여 하나의 짧은 비 모호한 형태로 사용할 수 있습니다 --file foo.txt또는 --file=foo.txt중 스타일, 사용 -m 4096또는 -m4096스타일, 임의의 순서로 옵션이 아닌 옵션을 혼합 등 getopt인식 할 수 없거나 모호한 옵션이있는 경우 오류 메시지를 출력합니다.

참고 : 실제로 기능과 호출 규칙이 서로 다른 완전히 다른getopt 기본 버전 getopt과 GNU 버전이 getopt있습니다. 2 기본 getopt은 상당히 깨졌습니다 : 긴 옵션을 처리 할뿐만 아니라 인수 또는 빈 인수 내부에 포함 된 공백도 처리 할 수 ​​없지만 getopts올바른 작업을 수행합니다. 위 코드는 기본적으로 작동하지 않습니다 getopt. GNU getopt는 기본적으로 Linux에 설치되지만 Mac OS X 및 FreeBSD에는 별도로 설치해야합니다. Mac OS X의 경우 MacPorts ( http://www.macports.org ) sudo port install getopt를 설치 한 다음 GNU getopt(일반적으로로 /opt/local/bin) 를 설치 하고 /opt/local/bin셸 경로 앞에 있어야 합니다./usr/bin. FreeBSD에서 설치하십시오 misc/getopt.

자신의 프로그램에 대한 예제 코드를 수정하기위한 빠른 안내서 : 처음 몇 줄 중에서 모두 "보일러 판"은 호출하는 줄을 제외하고 동일하게 유지되어야합니다 getopt. 뒤에 프로그램 이름을 변경하고 뒤에 -n짧은 옵션을 지정 -o하고 뒤에 긴 옵션을 지정해야 --long합니다. 값을 취하는 옵션 뒤에 콜론을 넣으십시오.

마지막으로, set대신에 코드가 있으면 eval setBSD 용으로 작성된 것입니다 getopt. eval set두 가지 버전 모두에서 잘 작동 하는 스타일 을 사용하도록 변경해야 getopt하지만 일반 set은 GNU에서 제대로 작동하지 않습니다 getopt.

1 실제로 는 긴 이름의 옵션 getoptsksh93지원하지만이 쉘은 자주 사용되지 않습니다 bash. 에서 zsh사용 zparseopts이 기능을 얻을 수 있습니다.

2 기술적으로 "GNU getopt"는 잘못된 이름입니다. 이 버전은 실제로 GNU 프로젝트가 아닌 Linux 용으로 작성되었습니다. 그러나 모든 GNU 규칙을 따르며 getopt" GNU " 라는 용어 가 일반적으로 사용됩니다 (예 : FreeBSD에서).


3
이것은 매우 유용했습니다. getopt를 사용하여 옵션을 확인한 다음 매우 간단한 루프에서 해당 옵션을 처리한다는 아이디어는 긴 스타일 옵션을 bash 스크립트에 추가하려고 할 때 실제로 잘 작동했습니다. 감사.
ianmjones

2
getopt리눅스 에서 GNU 유틸리티 는 아니고 전통 getopt은 BSD에서 온 것이 아니라 AT & T Unix에서 온 것입니다. ksh93 getopts(AT & T)은 GNU 스타일의 긴 옵션을 지원합니다.
Stephane Chazelas

@StephaneChazelas-의견을 반영하도록 편집되었습니다. 이 버전은 GNU 규칙을 따르고 일반적으로 GNU 프로그램 (예 :)을 사용하는 것처럼 작동 POSIXLY_CORRECT하지만 "Linux- 강화 getopt"는이 버전이 오직 리눅스.
Urban Vagabond

1
이 패키지는 util-linux 패키지에서 제공되므로 소프트웨어 번들이 Linux 전용 ( getopt다른 Unices로 쉽게 이식 될 수 있지만 다른 많은 소프트웨어는 Linux 전용)을위한 것이기 때문에 Linux 전용 util-linux입니다. GNU getopt (3)를 사용하는 비 GNU 프로그램은 understand $POSIX_CORRECT입니다. 예를 들어, 당신은 그것이 aplay단지 그런 이유에서 GNU 라고 말하지 않을 것입니다 . FreeBSD가 GNU getopt를 언급 할 때 GNU getopt (3) C API를 의미한다고 생각합니다.
Stephane Chazelas

@StephaneChazelas-FreeBSD는 getoptgetopt (3)가 아닌 util 을 명확하게 나타내는 "빌드 종속성 : GNU getopt를 설치하십시오"라는 오류 메시지가 있습니다.
Urban Vagabond

202

Bash 내장 getopts 함수는 대시 문자와 콜론을 optspec에 넣어 긴 옵션을 구문 분석하는 데 사용할 수 있습니다.

#!/usr/bin/env bash 
optspec=":hv-:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        -)
            case "${OPTARG}" in
                loglevel)
                    val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
                    echo "Parsing option: '--${OPTARG}', value: '${val}'" >&2;
                    ;;
                loglevel=*)
                    val=${OPTARG#*=}
                    opt=${OPTARG%=$val}
                    echo "Parsing option: '--${opt}', value: '${val}'" >&2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h)
            echo "usage: $0 [-v] [--loglevel[=]<value>]" >&2
            exit 2
            ;;
        v)
            echo "Parsing option: '-${optchar}'" >&2
            ;;
        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done

현재 작업 디렉토리getopts_test.sh 에서 실행 파일 이름 = 에 복사 한 후 다음 과 같은 출력을 생성 할 수 있습니다

$ ./getopts_test.sh
$ ./getopts_test.sh -f
Non-option argument: '-f'
$ ./getopts_test.sh -h
usage: code/getopts_test.sh [-v] [--loglevel[=]<value>]
$ ./getopts_test.sh --help
$ ./getopts_test.sh -v
Parsing option: '-v'
$ ./getopts_test.sh --very-bad
$ ./getopts_test.sh --loglevel
Parsing option: '--loglevel', value: ''
$ ./getopts_test.sh --loglevel 11
Parsing option: '--loglevel', value: '11'
$ ./getopts_test.sh --loglevel=11
Parsing option: '--loglevel', value: '11'

분명히 getopts OPTERR는 긴 옵션에 대해 검사 또는 옵션 인수 구문 분석을 수행 하지 않습니다. 위의 스크립트 조각은이를 수동으로 수행하는 방법을 보여줍니다. 기본 원칙은 데비안 Almquist 쉘 ( "대시")에서도 작동합니다. 특수한 경우를 참고하십시오.

getopts -- "-:"  ## without the option terminator "-- " bash complains about "-:"
getopts "-:"     ## this works in the Debian Almquist shell ("dash")

http://mywiki.wooledge.org/BashFAQ 에서 GreyCat이 처음부터 지적했듯이이 트릭은 옵션 인수를 허용하는 쉘의 비표준 동작 (예 : "-f filename"의 파일 이름)을 이용합니다. "-ffilename"에서와 같이 옵션에 연결됩니다. POSIX의 "- longoption"옵션 구문 분석을 종료 할 비 옵션 인수에 모든 longoptions를 켜 표준의 경우 그 사이에 공간이 있어야합니다 말한다.


2
질문 하나 :의 의미 무엇 !val="${!OPTIND}?
TomRoche

2
@TomRoche 그것은 간접 대체입니다 : unix.stackexchange.com/a/41293/84316
ecbrodie

2
@ecbrodie : 두 개의 인수가 하나가 아니라 실제로 처리 되었기 때문입니다. 첫 번째 인수는 "loglevel"이라는 단어이고 다음은 해당 인수에 대한 인수입니다. 한편, getopts자동으로 OPTIND1 씩 증가 하지만 우리의 경우에는 2 씩 증가시켜야하므로 수동으로 1 getopts씩 증가시킨 다음 자동으로 다시 1 씩 증가시킵니다.
Victor Zamanian

3
우리는 여기에서 bash 평형에 빠지기 때문에 : 알몸 변수 이름은 산술 표현식 내에서 허용되며, $필요 하지 않습니다. OPTIND=$(( $OPTIND + 1 ))그냥 될 수 있습니다 OPTIND=$(( OPTIND + 1 )). 더 흥미롭게도 산술 표현식 내에서 변수를 할당하고 늘릴 수 있으므로 더 축약 : $(( ++OPTIND ))하거나 항상 긍정적 인 (( ++OPTIND ))것을 고려 ++OPTIND하여 -e옵션을 사용하여 쉘 실행을 트립하지 않을 수 있습니다. :-) gnu.org/software/bash/manual/html_node/Shell-Arithmetic.html
clacke

3
--very-bad경고를 하지 않습니까?
Tom Hale

148

내장 getopts명령은 여전히 ​​AFAIK이며 단일 문자 옵션으로 만 제한됩니다.

getopt파싱하기 쉽도록 옵션 세트를 재구성 하는 외부 프로그램 이 있습니다. 긴 옵션도 처리하도록 해당 디자인을 조정할 수 있습니다. 사용법 예 :

aflag=no
bflag=no
flist=""
set -- $(getopt abf: "$@")
while [ $# -gt 0 ]
do
    case "$1" in
    (-a) aflag=yes;;
    (-b) bflag=yes;;
    (-f) flist="$flist $2"; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*)  break;;
    esac
    shift
done

# Process remaining non-option arguments
...

getoptlong명령 과 함께 비슷한 구성표를 사용할 수 있습니다 .

외부 getopt프로그램 의 근본적인 약점 은 공백이있는 인수를 처리하고 해당 공백을 정확하게 보존하는 데 어려움이 있다는 점에 유의하십시오 . 이것이 getopts단일 문자 옵션 만 처리한다는 사실에 의해 제한 되기는하지만 내장 기능 이 우수한 이유 입니다.


11
다른 호출 규칙이있는 GNU 버전을 제외하고 getopt는 근본적으로 손상되었습니다. 사용하지 마십시오. 대신 ** getopts로 사용하십시오 bash-hackers.org/wiki/doku.php/howto/getopts_tutorial
헨 드리

9
@hendry-자신의 링크에서 : "getopts는 GNU 스타일의 긴 옵션 (--myoption) 또는 XF86 스타일의 긴 옵션 (-myoption)을 구문 분석 할 수 없습니다!"
Tom Auger

1
Jonathan- eval setGNU getopt (Linux의 기본값)에서도 올바르게 작동하고 공백을 올바르게 처리하도록 따옴표와 함께 사용하도록 예제를 다시 작성해야 합니다 (아래 답변 참조).
Urban Vagabond

@UrbanVagabond : 왜 그렇게해야할지 모르겠습니다. 이 질문에는 Linux가 아닌 Unix라는 태그가 붙어 있습니다. 나는 전통적인 메커니즘을 의도적으로 보여주고 있으며, 인수 등에 공백이있는 문제가 있습니다. 원한다면 최신 Linux 버전을 시연 할 수 있으며 대답은 그렇게합니다. (나는의 사용이 있음, passim주의 ${1+"$@"}. 기이이고 Linux에서 찾을 것 어떤 쉘 특히 현대 껍질에 필요한이고 무엇와 확률에 참조 $ 1을 (를) 사용 : + "$를 @"}에 / 빈 / SH A의를
Jonathan Leffler

때문에 그것을해야 eval setGNU와 BSD 모두 올바른 일을 getopt일반 반면, set오직 BSD와 올바른 일을한다 getopt. 따라서 eval set사람들이이 일을하는 습관을 갖도록 격려하기 위해 사용할 수도 있습니다 . BTW 덕분에 ${1+"$@"}더 이상 필요 하지 않다는 것을 알지 못했습니다 . Mac OS X과 Linux 모두에서 작동하는 것들을 작성해야합니다. 둘 사이에서 많은 이식성이 필요합니다. 난 그냥 확인하고 "$@"실제로 모두에 옳은 일을합니까 sh, bash, ksh, 그리고 zsh맥 OS X에서; 물론 리눅스에서도 마찬가지입니다.
Urban Vagabond

78

긴 옵션과 함께 getopt를 실제로 사용하는 예는 다음과 같습니다.

aflag=no
bflag=no
cargument=none

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc: -l along,blong,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag="yes" ;;
    -b|--blong) bflag="yes" ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

1
eval setGNU getopt (Linux의 기본값)에서도 올바르게 작동하고 공백을 올바르게 처리 할 수 ​​있도록 따옴표와 함께 사용할 예제를 다시 작성해야 합니다 (아래 답변 참조).
Urban Vagabond

2
이것은 getopt질문이있는 동안 사용 getopts하고 있습니다.
Niklas Berglund 12

1
인가 (--, (-*(*유효한 패턴? 그들은 어떻게 다르다 --, -*그리고 *?
Maëlan

1
@ Maëlan – 선행 여는 괄호는 선택 사항이므로 스탠자 에서와 (--)동일합니다 . 선택적인 선도적 인 parens의 불균일 한 들여 쓰기 및 일관성없는 사용을 보는 것이 이상하지만 대답의 현재 코드는 나에게 유효합니다. --)case
Adam Katz

59

표준 getopts내장에서는 -"옵션" 의 " 인수"로 긴 옵션을 구문 분석 할 수 있습니다.

이것은 이식 가능한 기본 POSIX 셸이므로 외부 프로그램이나 bashism이 필요하지 않습니다.

받는 인수로이 가이드 구현 긴 옵션 -옵션은, 그래서 --alpha으로 볼 getopts-인수 alpha--bravo=foo같이 보인다 -인수 bravo=foo. 간단한 인수로 진정한 인수를 얻을 수 있습니다 ${OPTARG#*=}.

이 예에서 -b-c(및 긴 형식 --bravo--charlie)에는 필수 인수가 있습니다. 긴 옵션에 대한 인수는 등호 뒤에옵니다 --bravo=foo( 예 : 긴 옵션에 대한 공백 구분 기호는 구현하기 어렵습니다 (아래 참조)).

이것은 사용하기 때문에 getopts내장을 , 같은이 솔루션 지원 사용 cmd --bravo=foo -ac FILE(옵션 결합했다 -a그리고 -c여기에 하나 투쟁을하는 동안 대부분의 다른 답변을 인터리빙 표준 옵션 긴 옵션) 또는 실패는 그렇게 할 수 있습니다.

die() { echo "$*" >&2; exit 2; }  # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }

while getopts ab:c:-: OPT; do
  # support long options: https://stackoverflow.com/a/28466267/519360
  if [ "$OPT" = "-" ]; then   # long option: reformulate OPT and OPTARG
    OPT="${OPTARG%%=*}"       # extract long option name
    OPTARG="${OPTARG#$OPT}"   # extract long option argument (may be empty)
    OPTARG="${OPTARG#=}"      # if long option argument, remove assigning `=`
  fi
  case "$OPT" in
    a | alpha )    alpha=true ;;
    b | bravo )    needs_arg; bravo="$OPTARG" ;;
    c | charlie )  needs_arg; charlie="$OPTARG" ;;
    ??* )          die "Illegal option --$OPT" ;;  # bad long option
    \? )           exit 2 ;;  # bad short option (error reported via getopts)
  esac
done
shift $((OPTIND-1)) # remove parsed options and args from $@ list

옵션이 대시 ( -)이면 긴 옵션입니다. getopts실제 long 옵션 ( $OPTARG예 : --bravo=foo원래 sets OPT='-'및) 을 구문 분석했습니다 OPTARG='bravo=foo'. if스 D 자 세트 $OPT의 내용에 $OPTARG최초의 등호 기호 전에 ( bravo다음 예에서)과의 처음부터 것을 제거 $OPTARG(항복하지 =foo아니가있는 경우이 단계, 또는 빈 문자열 =). 마지막으로, 논증의 선두를 제거합니다 =. 이 시점에서 $OPT짧은 옵션 (한 문자) 또는 긴 옵션 (2 자 이상)입니다.

그런 case다음 짧거나 긴 옵션과 일치합니다. 짧은 옵션의 경우 getopts옵션과 누락 된 인수에 대해 자동으로 불평하므로 비어 needs_arg있을 때 치명적으로 종료되는 함수를 사용하여 수동으로 복제해야합니다 $OPTARG. ??*조건은 남아있는 긴 옵션 (일치 ?하나의 문자와 일치와 *일치하는 0 개 이상의, 그래서 ??*우리가 종료하기 전에 "잘못된 옵션"오류를 발행 할 수 있도록 2 + 문자와 일치).

(대문자 변수 이름에 대한 참고 사항 : 일반적으로 시스템 사용을 위해 모든 대문자 변수를 예약하는 것이 좋습니다. 나는 $OPT이를 대문자로 유지하기 위해이를 대문자로 유지하고 $OPTARG있지만,이 규칙을 위반합니다. 이것은 시스템이 수행해야 할 일이기 때문에 적합하며 그러한 변수를 사용하는 표준 (afaik)이 없기 때문에 안전해야합니다.)


긴 옵션에 대한 예기치 않은 인수에 대해 불평하려면 필수 인수에 대해 수행 한 작업을 모방하십시오. 도우미 함수를 사용하십시오. 예상치 못한 인수에 대해 불평하기 위해 테스트를 뒤집기 만하면됩니다.

no_arg() { if [ -n "$OPTARG" ]; then die "No arg allowed for --$OPT option"; fi; }

이 답변이전 버전은 공백으로 구분 된 인수로 긴 옵션을 수락하려고 시도했지만 신뢰할 수 없었습니다. getopts인수가 범위를 벗어 $OPTIND났고 모든 쉘에서 수동 증분 이 작동하지 않는다는 가정하에 조기 종료 할 수 있습니다.

이것은 다음 기술 중 하나를 사용하여 수행됩니다.

그리고 다음과 같이 결론지었습니다. [ $# -gt $OPTIND ] && OPTIND=$((OPTIND+1))


아주 좋은 자체 포함 된 솔루션. 한 가지 질문 : letter-c인수가 필요하지 않으므로 letter-c)? 를 사용하는 것만으로는 충분하지 않습니다 .; *중복 되는 것 같습니다.
Philip Kearns

1
@Arne 위치 인수는 잘못된 UX입니다. 이해하기 어려우며 선택적 인수는 지저분합니다. getopts첫 번째 위치 인수에서 다루지 않습니다. 이것은 예를 들어 자신의 주장과 하위 명령을 할 수 있습니다 git diff --color내가 해석하는 것, 그래서 command --foo=moo bar --baz waz필요로 --foo에 인수로 command하고 --baz waz받는 (옵션) 인수로 bar하위 명령. 이것은 위의 코드로 수행 할 수 있습니다. 나는 인수가 필요 --bravo -blah하기 때문에 거부 --bravo하고 -blah다른 옵션이 아닌 것이 확실하지 않습니다.
Adam Katz

1
UX에 동의하지 않습니다. 위치 인수는 개수를 제한하지 않는 한 유용합니다 (최대 2 개 또는 1 개와 같은 N 형). 사용자가 단계별로 명령을 작성할 수 있기 때문에 키워드 인수로 이들을 산포 할 수 있어야합니다 (예 : ls abc -la).
Arne Babenhauserheide

1
@AdamKatz : 나는 이것으로 작은 기사를 썼다 : draketo.de/english/free-software/shell-argument-parsing — 후행 옵션을 포착하기 위해 나머지 인수를 반복해서 읽는 것을 포함한다.
Arne Babenhauserheide

1
@ ArneBabenhauserheide : 공백으로 구분 된 인수를 지원하기 위해이 답변을 업데이트했습니다. evalPOSIX 셸에 필요하기 때문에 나머지 답변 아래에 나열되어 있습니다.
Adam Katz

33

이식 가능한 쉘 라이브러리 인 shFlags 를 살펴보십시오 (즉, Linux, Solaris 등의 경우 sh, bash, dash, ksh, zsh).

스크립트에 한 줄을 추가하는 것만 큼 간단하게 새 플래그를 추가 할 수 있으며 자동 생성 사용 기능을 제공합니다.

다음은 shFlag를Hello, world! 사용한 간단한 것입니다. .

#!/bin/sh

# source shflags from current directory
. ./shflags

# define a 'name' command-line string flag
DEFINE_string 'name' 'world' 'name to say hello to' 'n'

# parse the command-line
FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"

# say hello
echo "Hello, ${FLAGS_name}!"

긴 옵션 (예 : Linux)을 지원하는 향상된 getopt가있는 OS의 경우 다음을 수행 할 수 있습니다.

$ ./hello_world.sh --name Kate
Hello, Kate!

나머지는 짧은 옵션을 사용해야합니다.

$ ./hello_world.sh -n Kate
Hello, Kate!

새 플래그를 추가하는 것은 새 플래그를 추가하는 것만 큼 간단 DEFINE_ call합니다.


2
이것은 환상적이지만 불행히도 내 getopt (OS X)는 인수에서 공백을 지원하지 않습니다 : / 대안이 있는지 궁금합니다.
Alastair Stuart

@AlastairStuart-OS X에는 대안이 있습니다. MacPorts를 사용하여 GNU getopt를 설치하십시오 (일반적으로 / opt / local / bin / getopt에 설치됩니다).
Urban Vagabond

3
@UrbanVagabond – 불행히도 시스템이 아닌 기본 도구를 설치하는 것은 충분히 이식 가능한 도구에 대한 수용 가능한 요구 사항이 아닙니다.
Alastair 스튜어트

@AlastairStuart – GNU getopt 대신 getopts 내장을 사용하는 휴대용 솔루션에 대한 나의 답변 을 참조하십시오 . 기본 getopts 사용법과 동일하지만 긴 옵션에 대한 추가 반복이 있습니다.
Adam Katz

31

사용 getopts짧고 긴 옵션 및 인수와 함께


다음과 같은 모든 조합에서 작동합니다.

  • foobar -f --bar
  • foobar --foo -b
  • foobar -bf --bar --foobar
  • foobar -fbFBAshorty --bar -FB-인수 = 롱혼
  • foobar -fA "텍스트 shorty"-B --arguments = "텍스트 longhorn"
  • 배쉬 foobar -F --barfoo
  • sh foobar -B --foobar-...
  • bash ./foobar -F --bar

이 예제에 대한 일부 선언

Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

사용법 함수의 모양

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
  $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                  -f   --foo            Set foo to yes    ($bart)
                  -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

getops 긴 / 짧은 플래그와 긴 인수

while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
             --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

산출

##################################################################
echo "----------------------------------------------------------"
echo "RESULT short-foo      : $sfoo                                    long-foo      : $lfoo"
echo "RESULT short-bar      : $sbar                                    long-bar      : $lbar"
echo "RESULT short-foobar   : $sfoobar                                 long-foobar   : $lfoobar"
echo "RESULT short-barfoo   : $sbarfoo                                 long-barfoo   : $lbarfoo"
echo "RESULT short-arguments: $sarguments  with Argument = \"$sARG\"   long-arguments: $larguments and $lARG"

위의 내용을 응집력있는 스크립트로 결합

#!/bin/bash
# foobar: getopts with short and long options AND arguments

function _cleanup ()
{
  unset -f _usage _cleanup ; return 0
}

## Clear out nested functions on exit
trap _cleanup INT EXIT RETURN

###### some declarations for this example ######
Options=$@
Optnum=$#
sfoo='no '
sbar='no '
sfoobar='no '
sbarfoo='no '
sarguments='no '
sARG=empty
lfoo='no '
lbar='no '
lfoobar='no '
lbarfoo='no '
larguments='no '
lARG=empty

function _usage() 
{
  ###### U S A G E : Help and ERROR ######
  cat <<EOF
   foobar $Options
   $*
          Usage: foobar <[options]>
          Options:
                  -b   --bar            Set bar to yes    ($foo)
                    -f   --foo            Set foo to yes    ($bart)
                      -h   --help           Show this message
                  -A   --arguments=...  Set arguments to yes ($arguments) AND get ARGUMENT ($ARG)
                  -B   --barfoo         Set barfoo to yes ($barfoo)
                  -F   --foobar         Set foobar to yes ($foobar)
EOF
}

[ $# = 0 ] && _usage "  >>>>>>>> no options given "

##################################################################    
#######  "getopts" with: short options  AND  long options  #######
#######            AND  short/long arguments               #######
while getopts ':bfh-A:BF' OPTION ; do
  case "$OPTION" in
    b  ) sbar=yes                       ;;
    f  ) sfoo=yes                       ;;
    h  ) _usage                         ;;   
    A  ) sarguments=yes;sARG="$OPTARG"  ;;
    B  ) sbarfoo=yes                    ;;
    F  ) sfoobar=yes                    ;;
    -  ) [ $OPTIND -ge 1 ] && optind=$(expr $OPTIND - 1 ) || optind=$OPTIND
         eval OPTION="\$$optind"
         OPTARG=$(echo $OPTION | cut -d'=' -f2)
         OPTION=$(echo $OPTION | cut -d'=' -f1)
         case $OPTION in
             --foo       ) lfoo=yes                       ;;
             --bar       ) lbar=yes                       ;;
             --foobar    ) lfoobar=yes                    ;;
             --barfoo    ) lbarfoo=yes                    ;;
             --help      ) _usage                         ;;
               --arguments ) larguments=yes;lARG="$OPTARG"  ;; 
             * )  _usage " Long: >>>>>>>> invalid options (long) " ;;
         esac
       OPTIND=1
       shift
      ;;
    ? )  _usage "Short: >>>>>>>> invalid options (short) "  ;;
  esac
done

하나 이상의 긴 인수 (-)로 작동하지 않습니다. 그것은 나를 위해 첫 번째 것을 읽는 것 같습니다.
Sinaesthetic

@ Sinaesthetic – 예, eval긴 옵션에 대한 간격 인수에 대한 접근 방식을 사용하고 특정 쉘에서는 신뢰할 수없는 것으로 나타났습니다 (하지만 bash와 함께 작동 할 것으로 예상되지만 eval) 긴 옵션 인수를 받아들이는 방법 과 공백을 사용하려는 시도는 내 대답 을 참조하십시오 =. 내 솔루션은 cut몇 번 사용 하는 동안 외부 전화를 걸지 않습니다 .
아담 카츠

24

또 다른 방법...

# translate long options to short
for arg
do
    delim=""
    case "$arg" in
       --help) args="${args}-h ";;
       --verbose) args="${args}-v ";;
       --config) args="${args}-c ";;
       # pass through anything else
       *) [[ "${arg:0:1}" == "-" ]] || delim="\""
           args="${args}${delim}${arg}${delim} ";;
    esac
done
# reset the translated args
eval set -- $args
# now we can process with getopt
while getopts ":hvc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        c)  source $OPTARG ;;
        \?) usage ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage
        ;;
    esac
done

1
$args재 할당에 공백이 필요하지 않습니까? 이것은 bashisms 없이도 수행 될 수 있지만이 코드는 옵션과 인수에서 공백을 잃을 것입니다 ( $delim트릭이 효과가 있다고 생각하지는 않습니다 ). 대신 실행할 수 있습니다 set 내부for 당신은 단지 첫 번째 반복에 빈 충분히주의 경우 루프. bashisms 가없는 안전한 버전 이 있습니다.
Adam Katz

18

나는 이런 식으로 해결했다.

# A string with command options
options=$@

# An array with all the arguments
arguments=($options)

# Loop index
index=0

for argument in $options
  do
    # Incrementing index
    index=`expr $index + 1`

    # The conditions
    case $argument in
      -a) echo "key $argument value ${arguments[index]}" ;;
      -abc) echo "key $argument value ${arguments[index]}" ;;
    esac
  done

exit;

내가 멍청한거야? getopt그리고 getopts혼란된다.


1
이것은 나를 위해 작동하는 것 같습니다.이 방법의 문제가 무엇인지 모르겠지만 간단 해 보이므로 다른 사람이 사용하지 않는 이유가 있어야합니다.
Billy Moon

1
@Billy 예, 매개 변수 등을 관리하는 데 스크립트를 사용하지 않기 때문에 간단합니다. 기본적으로 인수 문자열 ($ @)을 배열로 변환하고 반복합니다. 루프에서 현재 값이 키가되고 다음 값이 값이됩니다. 그렇게 간단합니다.

1
@ Theodore 나는 이것이 당신에게 도움이되어서 기쁘다! 나에게도 고통이었다. 관심이 있으시면

2
내가 본 가장 쉬운 방법입니다. expr 대신 i = $ (($ i + 1))을 사용하는 것과 같이 약간 변경했지만 개념은 기밀입니다.
Thomas Dignan

6
(: 예 : 당신은 전혀 멍청하지 않습니다,하지만 당신은 기능이 누락 될 수 있습니다 혼합 옵션을 인식 할 수 getopt는 (들) -ltr또는 -lt -r뿐만 아니라 -l -t -r). 또한 옵션 처리가 완료되면 몇 가지 오류 처리 기능과 처리 된 매개 변수를 쉽게 전환 할 수있는 방법을 제공합니다.
Olivier Dulac

14

getopt종속성을 원하지 않는 경우 다음을 수행 할 수 있습니다.

while test $# -gt 0
do
  case $1 in

  # Normal option processing
    -h | --help)
      # usage and help
      ;;
    -v | --version)
      # version info
      ;;
  # ...

  # Special cases
    --)
      break
      ;;
    --*)
      # error unknown (long) option $1
      ;;
    -?)
      # error unknown (short) option $1
      ;;

  # FUN STUFF HERE:
  # Split apart combined short options
    -*)
      split=$1
      shift
      set -- $(echo "$split" | cut -c 2- | sed 's/./-& /g') "$@"
      continue
      ;;

  # Done with options
    *)
      break
      ;;
  esac

  # for testing purposes:
  echo "$1"

  shift
done

물론 하나의 대시로 긴 스타일 옵션을 사용할 수 없습니다. 또한 단축 버전을 추가하려면 (예 : --verbose 대신 --verbos) 수동으로 추가해야합니다.

그러나 getopts긴 옵션과 함께 기능 을 얻으려는 경우 간단한 방법입니다.

나는 또한이 발췌 문장을 요점 에 넣었다 .


이것은 한 번에 하나의 긴 옵션으로 만 작동하는 것처럼 보이지만 내 요구를 충족했습니다. 감사합니다!
kingjeffrey 2012 년

특별한 경우 --)에는 shift ;빠진 것 같습니다 . 현재는 --옵션이 아닌 첫 번째 인수로 유지됩니다.
dgw

dgw가 --옵션이 필요 하다고 지적하지만 이것이 실제로 더 나은 대답이라고 생각합니다 shift. 대안은 플랫폼에 의존하는 버전 getopt이거나 getopts_long명령 시작시에만 짧은 옵션을 사용하도록 강요해야하기 때문에 더 좋습니다 (즉 getopts, 나중에 긴 옵션을 사용하고 나중에 처리하십시오). 그리고 완전한 통제.
Haravikk

이 대답은 나에게 우리가이보다 더 아무것도 할 수있는 일을 할 수있는 답변 수십 스레드 왜 궁금합니다 절대적으로 명확하고 간단한 솔루션을, 그리고 증명 이외 억 getopt에 대한 이유가있는 경우 (들) 사용 사례 자신.
Florian Heigl

11

내장 getopts은 이것을 할 수 없습니다. 이를 수행 할 수 있는 외부 getopt (1) 프로그램이 있지만 util-linux 패키지의 Linux에서만 얻을 수 있습니다 . getopt-parse.bash 예제 스크립트가 제공됩니다. .

getopts_long쉘 함수로 작성된 것도 있습니다.


3
이 프로그램 getopt은 1993 년 FreeBSD 버전 1.0에 포함되었으며 그 이후로 FreeBSD의 일부였습니다. 따라서 FreeBSD 4.x에서 Apple의 Darwin 프로젝트에 포함되도록 채택되었습니다. OS X 10.6.8부터 Apple에 포함 된 매뉴얼 페이지는 FreeBSD 매뉴얼 페이지와 완전히 동일합니다. 예, OS X 및 Linux 이외의 다른 운영 체제에 포함되어 있습니다. 잘못된 정보에 대한이 답변에 -1입니다.
ghoti

8
#!/bin/bash
while getopts "abc:d:" flag
do
  case $flag in
    a) echo "[getopts:$OPTIND]==> -$flag";;
    b) echo "[getopts:$OPTIND]==> -$flag";;
    c) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
    d) echo "[getopts:$OPTIND]==> -$flag $OPTARG";;
  esac
done

shift $((OPTIND-1))
echo "[otheropts]==> $@"

exit

.

#!/bin/bash
until [ -z "$1" ]; do
  case $1 in
    "--dlong")
      shift
      if [ "${1:1:0}" != "-" ]
      then
        echo "==> dlong $1"
        shift
      fi;;
    *) echo "==> other $1"; shift;;
  esac
done
exit

2
설명이 좋을 것입니다. 첫 번째 스크립트는 짧은 옵션 만 허용하고 두 번째 스크립트는 긴 옵션 인수 구문 분석에 버그가 있습니다. 변수는 "${1:0:1}"인수 # 1, 인덱스 0의 부분 문자열, 길이 1에 대한 것이어야합니다 . 이렇게하면 짧고 긴 옵션을 혼합 할 수 없습니다.
Adam Katz

7

에서 ksh93, getopts긴 이름을 지원 ...

while getopts "f(file):s(server):" flag
do
    echo "$flag" $OPTIND $OPTARG
done

또는 내가 찾은 자습서가 말했습니다. 사용해보십시오.


4
이것은 ksh93의 getopts 내장입니다. 이 구문 외에도, 더 짧은 구문을 사용하지 않고도 긴 옵션을 허용하는 더 복잡한 구문도 있습니다.
jilles

2
합리적인 답변. OP는 WHAT 쉘을 지정하지 않았습니다.
ghoti

6

나는 단지 쉘 스크립트를 작성하고 실제로는 실용적이지 않으므로 모든 의견을 부탁드립니다.

@Arvid Requate가 제안한 전략을 사용하여 일부 사용자 오류를 발견했습니다. 값을 포함하지 않은 사용자는 실수로 다음 옵션의 이름이 값으로 취급됩니다.

./getopts_test.sh --loglevel= --toc=TRUE

"loglevel"의 값이 "--toc = TRUE"로 표시됩니다. 이것은 피할 수 있습니다.

수동 구문 분석에 대한 http://mwiki.wooledge.org/BashFAQ/035 토론 에서 CLI의 사용자 오류 확인에 대한 몇 가지 아이디어를 수정했습니다 . "-"및 "-"인수를 모두 처리 할 때 오류 검사를 삽입했습니다.

그런 다음 구문을 다루기 시작 했으므로 여기의 오류는 원래 작성자가 아닌 내 잘못입니다.

내 접근 방식은 등호의 유무에 관계없이 오랫동안 입력하는 것을 선호하는 사용자에게 도움이됩니다. 즉, "--loglevel = 9"와 같은 "--loglevel 9"에 대한 응답이 있어야합니다. -/ space 방법에서는 사용자가 인수를 잊었는지 확실하게 알 수 없으므로 약간의 추측이 필요합니다.

  1. 사용자가 long / equal sign 형식 (--opt =)을 갖는 경우 인수가 제공되지 않았기 때문에 공백 뒤에 =가 있으면 오류가 발생합니다.
  2. 사용자에게 긴 / 공백 인수가있는 경우 (--opt) 인수가 뒤에 없거나 (명령 끝) 인수가 대시로 시작하면이 스크립트는 실패를 발생시킵니다.

이것부터 시작한다면, "--opt = value"와 "--opt value"형식 사이에 흥미로운 차이점이 있습니다. 등호를 사용하면 명령 줄 인수는 "opt = value"로 표시되고 문자열 구문 분석을 처리하는 작업은 "="에서 분리됩니다. 반대로 "--opt value"와 함께 인수 이름은 "opt"이며 명령 행에 다음 값을 제공해야하는 문제가 있습니다. 여기서 @Arvid Requate는 간접 참조 인 $ {! OPTIND}를 사용했습니다. 나는 아직도 그것을 잘 이해하지 못하고 BashFAQ의 의견은 그 스타일에 대해 경고하는 것 같습니다 ( http://mywiki.wooledge.org/BashFAQ/006 ). BTW, OPTIND = $ (($ OPTIND + 1))의 중요성에 대한 이전 포스터의 의견은 정확하지 않다고 생각합니다. 내 말은

이 스크립트의 최신 버전에서 플래그 -v는 VERBOSE 출력을 의미합니다.

"cli-5.sh"라는 파일에 파일을 저장하고 실행 가능하게 만들면 원하는 방식으로 작동하거나 실패합니다

./cli-5.sh  -v --loglevel=44 --toc  TRUE
./cli-5.sh  -v --loglevel=44 --toc=TRUE
./cli-5.sh --loglevel 7
./cli-5.sh --loglevel=8
./cli-5.sh -l9

./cli-5.sh  --toc FALSE --loglevel=77
./cli-5.sh  --toc=FALSE --loglevel=77

./cli-5.sh   -l99 -t yyy
./cli-5.sh   -l 99 -t yyy

다음은 사용자 intpu의 오류 검사 출력 예입니다.

$ ./cli-5.sh  --toc --loglevel=77
ERROR: toc value must not have dash at beginning
$ ./cli-5.sh  --toc= --loglevel=77
ERROR: value for toc undefined

OPTIND 및 OPTARG의 내부를 인쇄하므로 -v를 켜는 것이 좋습니다.

#/usr/bin/env bash

## Paul Johnson
## 20171016
##

## Combines ideas from
## /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
## by @Arvid Requate, and http://mwiki.wooledge.org/BashFAQ/035

# What I don't understand yet: 
# In @Arvid REquate's answer, we have 
# val="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
# this works, but I don't understand it!


die() {
    printf '%s\n' "$1" >&2
    exit 1
}

printparse(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'Parse: %s%s%s\n' "$1" "$2" "$3" >&2;
    fi
}

showme(){
    if [ ${VERBOSE} -gt 0 ]; then
        printf 'VERBOSE: %s\n' "$1" >&2;
    fi
}


VERBOSE=0
loglevel=0
toc="TRUE"

optspec=":vhl:t:-:"
while getopts "$optspec" OPTCHAR; do

    showme "OPTARG:  ${OPTARG[*]}"
    showme "OPTIND:  ${OPTIND[*]}"
    case "${OPTCHAR}" in
        -)
            case "${OPTARG}" in
                loglevel) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) # CAUTION! no effect?
                    printparse "--${OPTARG}" "  " "${val}"
                    loglevel="${val}"
                    shift
                    ;;
                loglevel=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        printparse "--${opt}" "=" "${val}"
                        loglevel="${val}"
                        ## shift CAUTION don't shift this, fails othewise
                    else
                        die "ERROR: $opt value must be supplied"
                    fi
                    ;;
                toc) #argument has no equal sign
                    opt=${OPTARG}
                    val="${!OPTIND}"
                    ## check value. If negative, assume user forgot value
                    showme "OPTIND is {$OPTIND} {!OPTIND} has value \"${!OPTIND}\""
                    if [[ "$val" == -* ]]; then
                        die "ERROR: $opt value must not have dash at beginning"
                    fi
                    ## OPTIND=$(( $OPTIND + 1 )) #??
                    printparse "--${opt}" " " "${val}"
                    toc="${val}"
                    shift
                    ;;
                toc=*) #argument has equal sign
                    opt=${OPTARG%=*}
                    val=${OPTARG#*=}
                    if [ "${OPTARG#*=}" ]; then
                        toc=${val}
                        printparse "--$opt" " -> " "$toc"
                        ##shift ## NO! dont shift this
                    else
                        die "ERROR: value for $opt undefined"
                    fi
                    ;;

                help)
                    echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
                    exit 2
                    ;;
                *)
                    if [ "$OPTERR" = 1 ] && [ "${optspec:0:1}" != ":" ]; then
                        echo "Unknown option --${OPTARG}" >&2
                    fi
                    ;;
            esac;;
        h|-\?|--help)
            ## must rewrite this for all of the arguments
            echo "usage: $0 [-v] [--loglevel[=]<value>] [--toc[=]<TRUE,FALSE>]" >&2
            exit 2
            ;;
        l)
            loglevel=${OPTARG}
            printparse "-l" " "  "${loglevel}"
            ;;
        t)
            toc=${OPTARG}
            ;;
        v)
            VERBOSE=1
            ;;

        *)
            if [ "$OPTERR" != 1 ] || [ "${optspec:0:1}" = ":" ]; then
                echo "Non-option argument: '-${OPTARG}'" >&2
            fi
            ;;
    esac
done



echo "
After Parsing values
"
echo "loglevel  $loglevel" 
echo "toc  $toc"

OPTIND=$(( $OPTIND + 1 )): OPTIND의 매개 변수를 'gobble'할 때마다 필요합니다 (예 : 사용 된 경우 --toc value : value가 매개 변수 번호 $ OPTIND에 있습니다. toc의 값을 검색하면 구문 분석 할 다음 매개 변수가 값이 아님을 getopts에 알려야합니다. 그러나 그 이후 (따라서 : OPTIND=$(( $OPTIND + 1 )) . 및 스크립트 (및 참조하는 스크립트)가 누락되었습니다)가 완료된 후 : shift $(( $OPTIND -1 ))(매개 변수 1을 OPTIND-1로 구문 분석 한 후 getopts가 종료되었으므로 밖으로 이동해야합니다. $@지금 어떤 "비 옵션"매개 변수가 남아
올리비에 Dulac

오, 스스로를 움직일 때 getopts 아래의 매개 변수를 "이동"하므로 OPTIND는 항상 올바른 것을 가리키고 있지만 매우 혼란 스럽습니다. getopts while 루프 이후에 시프트 $ (($ OPTIND-1))가 여전히 필요하다고 생각하므로 (지금 스크립트를 테스트 할 수는 없음) $ 1은 이제 원래 $ 1 (옵션)을 가리 키지 않지만 나머지 인수 중 첫 번째 인수 (모든 옵션과 해당 값 다음에 나오는 인수) 예 : myrm -foo -bar = baz thisarg thenonethenanother
Olivier Dulac

5

바퀴의 또 다른 버전을 발명 ...

이 기능은 GNU getopt를 대체하는 POSIX 호환 플레인 버논 쉘입니다. 필수 / 선택 / 인수를 받아 들일 수있는 short / long 옵션을 지원하며 옵션 지정 방식은 GNU getopt와 거의 동일하므로 변환이 쉽지 않습니다.

물론 이것은 여전히 ​​스크립트에 삽입 할 수있는 상당한 크기의 코드이지만, 잘 알려진 getopt_long 쉘 함수의 약 절반에 해당하며 기존 GNU getopt 사용을 대체하려는 경우에 바람직 할 수 있습니다.

이것은 매우 새로운 코드이므로 YMMV입니다 (이것이 실제로 POSIX와 호환되지 않는지 알려주세요. 이식성은 처음부터 의도 였지만 유용한 POSIX 테스트 환경은 없습니다).

코드 및 예제 사용법은 다음과 같습니다.

#!/bin/sh
# posix_getopt shell function
# Author: Phil S.
# Version: 1.0
# Created: 2016-07-05
# URL: http://stackoverflow.com/a/37087374/324105

# POSIX-compatible argument quoting and parameter save/restore
# http://www.etalabs.net/sh_tricks.html
# Usage:
# parameters=$(save "$@") # save the original parameters.
# eval "set -- ${parameters}" # restore the saved parameters.
save () {
    local param
    for param; do
        printf %s\\n "$param" \
            | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"
    done
    printf %s\\n " "
}

# Exit with status $1 after displaying error message $2.
exiterr () {
    printf %s\\n "$2" >&2
    exit $1
}

# POSIX-compatible command line option parsing.
# This function supports long options and optional arguments, and is
# a (largely-compatible) drop-in replacement for GNU getopt.
#
# Instead of:
# opts=$(getopt -o "$shortopts" -l "$longopts" -- "$@")
# eval set -- ${opts}
#
# We instead use:
# opts=$(posix_getopt "$shortopts" "$longopts" "$@")
# eval "set -- ${opts}"
posix_getopt () { # args: "$shortopts" "$longopts" "$@"
    local shortopts longopts \
          arg argtype getopt nonopt opt optchar optword suffix

    shortopts="$1"
    longopts="$2"
    shift 2

    getopt=
    nonopt=
    while [ $# -gt 0 ]; do
        opt=
        arg=
        argtype=
        case "$1" in
            # '--' means don't parse the remaining options
            ( -- ) {
                getopt="${getopt}$(save "$@")"
                shift $#
                break
            };;
            # process short option
            ( -[!-]* ) {         # -x[foo]
                suffix=${1#-?}   # foo
                opt=${1%$suffix} # -x
                optchar=${opt#-} # x
                case "${shortopts}" in
                    ( *${optchar}::* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *${optchar}:* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 "$1 requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 "$1 requires an argument";;
                            esac
                        fi
                    };;
                    ( *${optchar}* ) { # no argument
                        argtype=none
                        arg=
                        shift
                        # Handle multiple no-argument parameters combined as
                        # -xyz instead of -x -y -z. If we have just shifted
                        # parameter -xyz, we now replace it with -yz (which
                        # will be processed in the next iteration).
                        if [ -n "${suffix}" ]; then
                            eval "set -- $(save "-${suffix}")$(save "$@")"
                        fi
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # process long option
            ( --?* ) {            # --xarg[=foo]
                suffix=${1#*=}    # foo (unless there was no =)
                if [ "${suffix}" = "$1" ]; then
                    suffix=
                fi
                opt=${1%=$suffix} # --xarg
                optword=${opt#--} # xarg
                case ",${longopts}," in
                    ( *,${optword}::,* ) { # optional argument
                        argtype=optional
                        arg="${suffix}"
                        shift
                    };;
                    ( *,${optword}:,* ) { # required argument
                        argtype=required
                        if [ -n "${suffix}" ]; then
                            arg="${suffix}"
                            shift
                        else
                            case "$2" in
                                ( -* ) exiterr 1 \
                                       "--${optword} requires an argument";;
                                ( ?* ) arg="$2"; shift 2;;
                                (  * ) exiterr 1 \
                                       "--${optword} requires an argument";;
                            esac
                        fi
                    };;
                    ( *,${optword},* ) { # no argument
                        if [ -n "${suffix}" ]; then
                            exiterr 1 "--${optword} does not take an argument"
                        fi
                        argtype=none
                        arg=
                        shift
                    };;
                    ( * ) exiterr 1 "Unknown option $1";;
                esac
            };;
            # any other parameters starting with -
            ( -* ) exiterr 1 "Unknown option $1";;
            # remember non-option parameters
            ( * ) nonopt="${nonopt}$(save "$1")"; shift;;
        esac

        if [ -n "${opt}" ]; then
            getopt="${getopt}$(save "$opt")"
            case "${argtype}" in
                ( optional|required ) {
                    getopt="${getopt}$(save "$arg")"
                };;
            esac
        fi
    done

    # Generate function output, suitable for:
    # eval "set -- $(posix_getopt ...)"
    printf %s "${getopt}"
    if [ -n "${nonopt}" ]; then
        printf %s "$(save "--")${nonopt}"
    fi
}

사용법 예 :

# Process command line options
shortopts="hvd:c::s::L:D"
longopts="help,version,directory:,client::,server::,load:,delete"
#opts=$(getopt -o "$shortopts" -l "$longopts" -n "$(basename $0)" -- "$@")
opts=$(posix_getopt "$shortopts" "$longopts" "$@")
if [ $? -eq 0 ]; then
    #eval set -- ${opts}
    eval "set -- ${opts}"
    while [ $# -gt 0 ]; do
        case "$1" in
            ( --                ) shift; break;;
            ( -h|--help         ) help=1; shift; break;;
            ( -v|--version      ) version_help=1; shift; break;;
            ( -d|--directory    ) dir=$2; shift 2;;
            ( -c|--client       ) useclient=1; client=$2; shift 2;;
            ( -s|--server       ) startserver=1; server_name=$2; shift 2;;
            ( -L|--load         ) load=$2; shift 2;;
            ( -D|--delete       ) delete=1; shift;;
        esac
    done
else
    shorthelp=1 # getopt returned (and reported) an error.
fi

4

허용 된 답변은 bash built-in의 모든 단점을 지적하는 훌륭한 일을 getopts합니다. 대답은 다음과 같이 끝납니다.

따라서 긴 옵션에 대한 지원 부족을 해결하기 위해 더 많은 코드를 작성할 수는 있지만 훨씬 더 많은 작업이며 getopt 파서를 사용하여 코드를 단순화하는 목적을 부분적으로 무효화합니다.

그리고 나는 그 진술에 원칙적으로 동의하지만, 우리가 다양한 스크립트에서이 기능을 구현 한 횟수는 "표준화 된"테스트를 거친 솔루션을 만드는 데 약간의 노력을 기울이는 것을 정당화한다고 생각합니다.

따라서 외부 종속성없이 순수한 bash getopts로 구현 getopts_long하여 bash를 "업그레이드"했습니다 . 이 기능의 사용은 내장과 100 % 호환됩니다 getopts.

포함하여 getopts_long(되는 GitHub의에서 호스팅 스크립트에서), 원래의 질문에 대한 대답은 간단하게 구현 될 수 있습니다 :

source "${PATH_TO}/getopts_long.bash"

while getopts_long ':c: copyfile:' OPTKEY; do
    case ${OPTKEY} in
        'c'|'copyfile')
            echo 'file supplied -- ${OPTARG}'
            ;;
        '?')
            echo "INVALID OPTION -- ${OPTARG}" >&2
            exit 1
            ;;
        ':')
            echo "MISSING ARGUMENT for option -- ${OPTARG}" >&2
            exit 1
            ;;
        *)
            echo "Misconfigured OPTSPEC or uncaught option -- ${OPTKEY}" >&2
            exit 1
            ;;
    esac
done

shift $(( OPTIND - 1 ))
[[ "${1}" == "--" ]] && shift

3

아직 솔루션에 대해 의견을 말하거나 투표 할 담당자가 충분하지 않지만 sme의 답변 은 나에게 매우 효과적이었습니다. 내가 만난 유일한 문제는 인수가 작은 따옴표로 묶여 있다는 것입니다 (따라서 스트립이 있습니다).

또한 몇 가지 예제 사용법과 도움말 텍스트를 추가했습니다. 여기에 약간 확장 된 버전이 포함됩니다 :

#!/bin/bash

# getopt example
# from: /programming/402377/using-getopts-in-bash-shell-script-to-get-long-and-short-command-line-options
HELP_TEXT=\
"   USAGE:\n
    Accepts - and -- flags, can specify options that require a value, and can be in any order. A double-hyphen (--) will stop processing options.\n\n

    Accepts the following forms:\n\n

    getopt-example.sh -a -b -c value-for-c some-arg\n
    getopt-example.sh -c value-for-c -a -b some-arg\n
    getopt-example.sh -abc some-arg\n
    getopt-example.sh --along --blong --clong value-for-c -a -b -c some-arg\n
    getopt-example.sh some-arg --clong value-for-c\n
    getopt-example.sh
"

aflag=false
bflag=false
cargument=""

# options may be followed by one colon to indicate they have a required argument
if ! options=$(getopt -o abc:h\? -l along,blong,help,clong: -- "$@")
then
    # something went wrong, getopt will put out an error message for us
    exit 1
fi

set -- $options

while [ $# -gt 0 ]
do
    case $1 in
    -a|--along) aflag=true ;;
    -b|--blong) bflag=true ;;
    # for options with required arguments, an additional shift is required
    -c|--clong) cargument="$2" ; shift;;
    -h|--help|-\?) echo -e $HELP_TEXT; exit;;
    (--) shift; break;;
    (-*) echo "$0: error - unrecognized option $1" 1>&2; exit 1;;
    (*) break;;
    esac
    shift
done

# to remove the single quotes around arguments, pipe the output into:
# | sed -e "s/^'\\|'$//g"  (just leading/trailing) or | sed -e "s/'//g"  (all)

echo aflag=${aflag}
echo bflag=${bflag}
echo cargument=${cargument}

while [ $# -gt 0 ]
do
    echo arg=$1
    shift

    if [[ $aflag == true ]]; then
        echo a is true
    fi

done

3

bash에서 복잡한 옵션 구문 분석에 대한 몇 가지 접근 방식을 찾을 수 있습니다. http://mywiki.wooledge.org/ComplexOptionParsing

나는 다음 중 하나를 만들었으며 코드가 적고 길고 짧은 옵션이 모두 작동하기 때문에 좋은 것이라고 생각합니다. 긴 옵션은이 방법으로 여러 인수를 가질 수도 있습니다.

#!/bin/bash
# Uses bash extensions.  Not portable as written.

declare -A longoptspec
longoptspec=( [loglevel]=1 ) #use associative array to declare how many arguments a long option expects, in this case we declare that loglevel expects/has one argument, long options that aren't listed i n this way will have zero arguments by default
optspec=":h-:"
while getopts "$optspec" opt; do
while true; do
    case "${opt}" in
        -) #OPTARG is name-of-long-option or name-of-long-option=value
            if [[ "${OPTARG}" =~ .*=.* ]] #with this --key=value format only one argument is possible
            then
                opt=${OPTARG/=*/}
                OPTARG=${OPTARG#*=}
                ((OPTIND--))    
            else #with this --key value1 value2 format multiple arguments are possible
                opt="$OPTARG"
                OPTARG=(${@:OPTIND:$((longoptspec[$opt]))})
            fi
            ((OPTIND+=longoptspec[$opt]))
            continue #now that opt/OPTARG are set we can process them as if getopts would've given us long options
            ;;
        loglevel)
          loglevel=$OPTARG
            ;;
        h|help)
            echo "usage: $0 [--loglevel[=]<value>]" >&2
            exit 2
            ;;
    esac
break; done
done

# End of file

2

나는 그 주제에 대해 오랫동안 오랫동안 연구 해 왔으며 ... 내 자신의 라이브러리를 만들었고 메인 스크립트에서 소싱해야합니다. 예제는 libopt4shellcd2mpc 를 참조하십시오 . 그것이 도움이되기를 바랍니다!


2

개선 된 솔루션 :

# translate long options to short
# Note: This enable long options but disable "--?*" in $OPTARG, or disable long options after  "--" in option fields.
for ((i=1;$#;i++)) ; do
    case "$1" in
        --)
            # [ ${args[$((i-1))]} == ... ] || EndOpt=1 ;;& # DIRTY: we still can handle some execptions...
            EndOpt=1 ;;&
        --version) ((EndOpt)) && args[$i]="$1"  || args[$i]="-V";;
        # default case : short option use the first char of the long option:
        --?*) ((EndOpt)) && args[$i]="$1"  || args[$i]="-${1:2:1}";;
        # pass through anything else:
        *) args[$i]="$1" ;;
    esac
    shift
done
# reset the translated args
set -- "${args[@]}"

function usage {
echo "Usage: $0 [options] files" >&2
    exit $1
}

# now we can process with getopt
while getopts ":hvVc:" opt; do
    case $opt in
        h)  usage ;;
        v)  VERBOSE=true ;;
        V)  echo $Version ; exit ;;
        c)  source $OPTARG ;;
        \?) echo "unrecognized option: -$opt" ; usage -1 ;;
        :)
        echo "option -$OPTARG requires an argument"
        usage -1
        ;;
    esac
done

shift $((OPTIND-1))
[[ "$1" == "--" ]] && shift

2

긴 명령 행 옵션이 필요한 경우 getopts 부분에 대해서만 ksh를 사용하는 것이 더 간단 할 수 있습니다.

# Working Getopts Long => KSH

#! /bin/ksh
# Getopts Long
USAGE="s(showconfig)"
USAGE+="c:(createdb)"
USAGE+="l:(createlistener)"
USAGE+="g:(generatescripts)"
USAGE+="r:(removedb)"
USAGE+="x:(removelistener)"
USAGE+="t:(createtemplate)"
USAGE+="h(help)"

while getopts "$USAGE" optchar ; do
    case $optchar in
    s)  echo "Displaying Configuration" ;;
        c)  echo "Creating Database $OPTARG" ;;
    l)  echo "Creating Listener LISTENER_$OPTARG" ;;
    g)  echo "Generating Scripts for Database $OPTARG" ;;
    r)  echo "Removing Database $OPTARG" ;;
    x)  echo "Removing Listener LISTENER_$OPTARG" ;;
    t)  echo "Creating Database Template" ;;
    h)  echo "Help" ;;
    esac
done

+1-오픈 소스 AST 프로젝트 (AT & T Research)의 ksh93으로 제한됩니다.
Henk Langeveld

2

엄격한 bash 지원 (-u)을 사용하여 외부 종속성이없는 것을 원했으며 이전 bash 버전에서도 작동해야했습니다. 이것은 다양한 유형의 매개 변수를 처리합니다.

  • 짧은 부울 (-h)
  • 짧은 옵션 (-i "image.jpg")
  • 긴 바보 (-도움말)
  • 옵션과 동일 (--file = "filename.ext")
  • 공간 옵션 (--file "filename.ext")
  • 연결된 부울 (-hvm)

스크립트 상단에 다음을 삽입하십시오.

# Check if a list of params contains a specific param
# usage: if _param_variant "h|?|help p|path f|file long-thing t|test-thing" "file" ; then ...
# the global variable $key is updated to the long notation (last entry in the pipe delineated list, if applicable)
_param_variant() {
  for param in $1 ; do
    local variants=${param//\|/ }
    for variant in $variants ; do
      if [[ "$variant" = "$2" ]] ; then
        # Update the key to match the long version
        local arr=(${param//\|/ })
        let last=${#arr[@]}-1
        key="${arr[$last]}"
        return 0
      fi
    done
  done
  return 1
}

# Get input parameters in short or long notation, with no dependencies beyond bash
# usage:
#     # First, set your defaults
#     param_help=false
#     param_path="."
#     param_file=false
#     param_image=false
#     param_image_lossy=true
#     # Define allowed parameters
#     allowed_params="h|?|help p|path f|file i|image image-lossy"
#     # Get parameters from the arguments provided
#     _get_params $*
#
# Parameters will be converted into safe variable names like:
#     param_help,
#     param_path,
#     param_file,
#     param_image,
#     param_image_lossy
#
# Parameters without a value like "-h" or "--help" will be treated as
# boolean, and will be set as param_help=true
#
# Parameters can accept values in the various typical ways:
#     -i "path/goes/here"
#     --image "path/goes/here"
#     --image="path/goes/here"
#     --image=path/goes/here
# These would all result in effectively the same thing:
#     param_image="path/goes/here"
#
# Concatinated short parameters (boolean) are also supported
#     -vhm is the same as -v -h -m
_get_params(){

  local param_pair
  local key
  local value
  local shift_count

  while : ; do
    # Ensure we have a valid param. Allows this to work even in -u mode.
    if [[ $# == 0 || -z $1 ]] ; then
      break
    fi

    # Split the argument if it contains "="
    param_pair=(${1//=/ })
    # Remove preceeding dashes
    key="${param_pair[0]#--}"

    # Check for concatinated boolean short parameters.
    local nodash="${key#-}"
    local breakout=false
    if [[ "$nodash" != "$key" && ${#nodash} -gt 1 ]]; then
      # Extrapolate multiple boolean keys in single dash notation. ie. "-vmh" should translate to: "-v -m -h"
      local short_param_count=${#nodash}
      let new_arg_count=$#+$short_param_count-1
      local new_args=""
      # $str_pos is the current position in the short param string $nodash
      for (( str_pos=0; str_pos<new_arg_count; str_pos++ )); do
        # The first character becomes the current key
        if [ $str_pos -eq 0 ] ; then
          key="${nodash:$str_pos:1}"
          breakout=true
        fi
        # $arg_pos is the current position in the constructed arguments list
        let arg_pos=$str_pos+1
        if [ $arg_pos -gt $short_param_count ] ; then
          # handle other arguments
          let orignal_arg_number=$arg_pos-$short_param_count+1
          local new_arg="${!orignal_arg_number}"
        else
          # break out our one argument into new ones
          local new_arg="-${nodash:$str_pos:1}"
        fi
        new_args="$new_args \"$new_arg\""
      done
      # remove the preceding space and set the new arguments
      eval set -- "${new_args# }"
    fi
    if ! $breakout ; then
      key="$nodash"
    fi

    # By default we expect to shift one argument at a time
    shift_count=1
    if [ "${#param_pair[@]}" -gt "1" ] ; then
      # This is a param with equals notation
      value="${param_pair[1]}"
    else
      # This is either a boolean param and there is no value,
      # or the value is the next command line argument
      # Assume the value is a boolean true, unless the next argument is found to be a value.
      value=true
      if [[ $# -gt 1 && -n "$2" ]]; then
        local nodash="${2#-}"
        if [ "$nodash" = "$2" ]; then
          # The next argument has NO preceding dash so it is a value
          value="$2"
          shift_count=2
        fi
      fi
    fi

    # Check that the param being passed is one of the allowed params
    if _param_variant "$allowed_params" "$key" ; then
      # --key-name will now become param_key_name
      eval param_${key//-/_}="$value"
    else
      printf 'WARNING: Unknown option (ignored): %s\n' "$1" >&2
    fi
    shift $shift_count
  done
}

그리고 그렇게 사용하십시오 :

# Assign defaults for parameters
param_help=false
param_path=$(pwd)
param_file=false
param_image=true
param_image_lossy=true
param_image_lossy_quality=85

# Define the params we will allow
allowed_params="h|?|help p|path f|file i|image image-lossy image-lossy-quality"

# Get the params from arguments provided
_get_params $*

1

크로스 플랫폼 호환성을 유지하고 외부 실행 파일에 대한 의존을 피하기 위해 다른 언어에서 일부 코드를 이식했습니다.

사용하기가 매우 쉽다는 것을 알았습니다. 예는 다음과 같습니다.

ArgParser::addArg "[h]elp"    false    "This list"
ArgParser::addArg "[q]uiet"   false    "Supress output"
ArgParser::addArg "[s]leep"   1        "Seconds to sleep"
ArgParser::addArg "v"         1        "Verbose mode"

ArgParser::parse "$@"

ArgParser::isset help && ArgParser::showArgs

ArgParser::isset "quiet" \
   && echo "Quiet!" \
   || echo "Noisy!"

local __sleep
ArgParser::tryAndGetArg sleep into __sleep \
   && echo "Sleep for $__sleep seconds" \
   || echo "No value passed for sleep"

# This way is often more convienient, but is a little slower
echo "Sleep set to: $( ArgParser::getArg sleep )"

필요한 BASH는 예상보다 조금 길지만 BASH 4의 연관 배열에 의존하지 않기를 원했습니다. http://nt4.com/bash/argparser.inc.sh 에서 직접 다운로드 할 수도 있습니다.

#!/usr/bin/env bash

# Updates to this script may be found at
# http://nt4.com/bash/argparser.inc.sh

# Example of runtime usage:
# mnc.sh --nc -q Caprica.S0*mkv *.avi *.mp3 --more-options here --host centos8.host.com

# Example of use in script (see bottom)
# Just include this file in yours, or use
# source argparser.inc.sh

unset EXPLODED
declare -a EXPLODED
function explode 
{
    local c=$# 
    (( c < 2 )) && 
    {
        echo function "$0" is missing parameters 
        return 1
    }

    local delimiter="$1"
    local string="$2"
    local limit=${3-99}

    local tmp_delim=$'\x07'
    local delin=${string//$delimiter/$tmp_delim}
    local oldifs="$IFS"

    IFS="$tmp_delim"
    EXPLODED=($delin)
    IFS="$oldifs"
}


# See: http://fvue.nl/wiki/Bash:_Passing_variables_by_reference
# Usage: local "$1" && upvar $1 "value(s)"
upvar() {
    if unset -v "$1"; then           # Unset & validate varname
        if (( $# == 2 )); then
            eval $1=\"\$2\"          # Return single value
        else
            eval $1=\(\"\${@:2}\"\)  # Return array
        fi
    fi
}

function decho
{
    :
}

function ArgParser::check
{
    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        matched=0
        explode "|" "${__argparser__arglist[$i]}"
        if [ "${#1}" -eq 1 ]
        then
            if [ "${1}" == "${EXPLODED[0]}" ]
            then
                decho "Matched $1 with ${EXPLODED[0]}"
                matched=1

                break
            fi
        else
            if [ "${1}" == "${EXPLODED[1]}" ]
            then
                decho "Matched $1 with ${EXPLODED[1]}"
                matched=1

                break
            fi
        fi
    done
    (( matched == 0 )) && return 2
    # decho "Key $key has default argument of ${EXPLODED[3]}"
    if [ "${EXPLODED[3]}" == "false" ]
    then
        return 0
    else
        return 1
    fi
}

function ArgParser::set
{
    key=$3
    value="${1:-true}"
    declare -g __argpassed__$key="$value"
}

function ArgParser::parse
{

    unset __argparser__argv
    __argparser__argv=()
    # echo parsing: "$@"

    while [ -n "$1" ]
    do
        # echo "Processing $1"
        if [ "${1:0:2}" == '--' ]
        then
            key=${1:2}
            value=$2
        elif [ "${1:0:1}" == '-' ]
        then
            key=${1:1}               # Strip off leading -
            value=$2
        else
            decho "Not argument or option: '$1'" >& 2
            __argparser__argv+=( "$1" )
            shift
            continue
        fi
        # parameter=${tmp%%=*}     # Extract name.
        # value=${tmp##*=}         # Extract value.
        decho "Key: '$key', value: '$value'"
        # eval $parameter=$value
        ArgParser::check $key
        el=$?
        # echo "Check returned $el for $key"
        [ $el -eq  2 ] && decho "No match for option '$1'" >&2 # && __argparser__argv+=( "$1" )
        [ $el -eq  0 ] && decho "Matched option '${EXPLODED[2]}' with no arguments"        >&2 && ArgParser::set true "${EXPLODED[@]}"
        [ $el -eq  1 ] && decho "Matched option '${EXPLODED[2]}' with an argument of '$2'"   >&2 && ArgParser::set "$2" "${EXPLODED[@]}" && shift
        shift
    done
}

function ArgParser::isset
{
    declare -p "__argpassed__$1" > /dev/null 2>&1 && return 0
    return 1
}

function ArgParser::getArg
{
    # This one would be a bit silly, since we can only return non-integer arguments ineffeciently
    varname="__argpassed__$1"
    echo "${!varname}"
}

##
# usage: tryAndGetArg <argname> into <varname>
# returns: 0 on success, 1 on failure
function ArgParser::tryAndGetArg
{
    local __varname="__argpassed__$1"
    local __value="${!__varname}"
    test -z "$__value" && return 1
    local "$3" && upvar $3 "$__value"
    return 0
}

function ArgParser::__construct
{
    unset __argparser__arglist
    # declare -a __argparser__arglist
}

##
# @brief add command line argument
# @param 1 short and/or long, eg: [s]hort
# @param 2 default value
# @param 3 description
##
function ArgParser::addArg
{
    # check for short arg within long arg
    if [[ "$1" =~ \[(.)\] ]]
    then
        short=${BASH_REMATCH[1]}
        long=${1/\[$short\]/$short}
    else
        long=$1
    fi
    if [ "${#long}" -eq 1 ]
    then
        short=$long
        long=''
    fi
    decho short: "$short"
    decho long: "$long"
    __argparser__arglist+=("$short|$long|$1|$2|$3")
}

## 
# @brief show available command line arguments
##
function ArgParser::showArgs
{
    # declare -p | grep argparser
    printf "Usage: %s [OPTION...]\n\n" "$( basename "${BASH_SOURCE[0]}" )"
    printf "Defaults for the options are specified in brackets.\n\n";

    __args=${#__argparser__arglist[@]}
    for (( i=0; i<__args; i++ ))
    do
        local shortname=
        local fullname=
        local default=
        local description=
        local comma=

        explode "|" "${__argparser__arglist[$i]}"

        shortname="${EXPLODED[0]:+-${EXPLODED[0]}}" # String Substitution Guide: 
        fullname="${EXPLODED[1]:+--${EXPLODED[1]}}" # http://tldp.org/LDP/abs/html/parameter-substitution.html
        test -n "$shortname" \
            && test -n "$fullname" \
            && comma=","

        default="${EXPLODED[3]}"
        case $default in
            false )
                default=
                ;;
            "" )
                default=
                ;;
            * )
                default="[$default]"
        esac

        description="${EXPLODED[4]}"

        printf "  %2s%1s %-19s %s %s\n" "$shortname" "$comma" "$fullname" "$description" "$default"
    done
}

function ArgParser::test
{
    # Arguments with a default of 'false' do not take paramaters (note: default
    # values are not applied in this release)

    ArgParser::addArg "[h]elp"      false       "This list"
    ArgParser::addArg "[q]uiet" false       "Supress output"
    ArgParser::addArg "[s]leep" 1           "Seconds to sleep"
    ArgParser::addArg "v"           1           "Verbose mode"

    ArgParser::parse "$@"

    ArgParser::isset help && ArgParser::showArgs

    ArgParser::isset "quiet" \
        && echo "Quiet!" \
        || echo "Noisy!"

    local __sleep
    ArgParser::tryAndGetArg sleep into __sleep \
        && echo "Sleep for $__sleep seconds" \
        || echo "No value passed for sleep"

    # This way is often more convienient, but is a little slower
    echo "Sleep set to: $( ArgParser::getArg sleep )"

    echo "Remaining command line: ${__argparser__argv[@]}"

}

if [ "$( basename "$0" )" == "argparser.inc.sh" ]
then
    ArgParser::test "$@"
fi

1

모든 긴 옵션에 고유하고 일치하는 첫 문자가 짧은 옵션 인 경우, 예를 들어

./slamm --chaos 23 --plenty test -quiet

와 같다

./slamm -c 23 -p test -q

getars가 $ args를 다시 쓰기 전에 이것을 사용할 수 있습니다 .

# change long options to short options

for arg; do 
    [[ "${arg:0:1}" == "-" ]] && delim="" || delim="\""
    if [ "${arg:0:2}" == "--" ]; 
       then args="${args} -${arg:2:1}" 
       else args="${args} ${delim}${arg}${delim}"
    fi
done

# reset the incoming args
eval set -- $args

# proceed as usual
while getopts ":b:la:h" OPTION; do
    .....

영감을 얻은 mtvee에게 감사합니다 ;-)


나는 eval의 중요성을 여기에서
얻지 못했습니다

1

단순히 이것이 당신이 스크립트를 호출하는 방법이라면

myscript.sh --input1 "ABC" --input2 "PQR" --input2 "XYZ"

그런 다음 getopt 및 --longoptions를 사용하여 가장 간단한 방법으로이를 달성 할 수 있습니다.

이것을 시도하십시오, 이것이 유용하기를 바랍니다

# Read command line options
ARGUMENT_LIST=(
    "input1"
    "input2"
    "input3"
)



# read arguments
opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
)


echo $opts

eval set --$opts

while true; do
    case "$1" in
    --input1)  
        shift
        empId=$1
        ;;
    --input2)  
        shift
        fromDate=$1
        ;;
    --input3)  
        shift
        toDate=$1
        ;;
      --)
        shift
        break
        ;;
    esac
    shift
done

0

getopts는 인수가 없을 것으로 예상되는 한 긴 옵션을 구문 분석하기 위해 "사용될 수 있습니다"...

방법은 다음과 같습니다.

$ cat > longopt
while getopts 'e:-:' OPT; do
  case $OPT in
    e) echo echo: $OPTARG;;
    -) #long option
       case $OPTARG in
         long-option) echo long option;;
         *) echo long option: $OPTARG;;
       esac;;
  esac
done

$ bash longopt -e asd --long-option --long1 --long2 -e test
echo: asd
long option
long option: long1
long option: long2
echo: test

long 옵션에 대한 매개 변수를 얻기 위해 OPTIND를 사용하려고하면 getopts는이를 선택 사항이 아닌 첫 번째 선택적 위치 매개 변수로 취급하고 다른 매개 변수의 구문 분석을 중지합니다. 이 경우 간단한 case 문을 사용하여 수동으로 처리하는 것이 좋습니다.

이것은 "항상"작동합니다.

$ cat >longopt2
while (($#)); do
    OPT=$1
    shift
    case $OPT in
        --*) case ${OPT:2} in
            long1) echo long1 option;;
            complex) echo comples with argument $1; shift;;
        esac;;

        -*) case ${OPT:1} in
            a) echo short option a;;
            b) echo short option b with parameter $1; shift;;
        esac;;
    esac
done


$ bash longopt2 --complex abc -a --long -b test
comples with argument abc
short option a
short option b with parameter test

getopts만큼 유연하지는 않지만 사례 인스턴스 내에서 직접 오류 검사 코드를 수행해야합니다 ...

그러나 옵션입니다.


그러나 긴 옵션은 종종 논쟁을 요구합니다. 그리고 당신은 - 로 더 많은 일을 할 수 있습니다. 결국 그것은 그것이 그것을 기본적으로 지원하지 않는다면 그것을 구현하는 모든 방법은 해킹의 무언가이지만 그럼에도 불구하고 당신은 -를 확장 할 수 있다고 주장 할 수 있습니다. 그리고 예 시프트 는 매우 유용하지만 물론 인수를 기대하면 다음 인수 (사용자가 지정하지 않은 경우)가 예상 인수의 일부가 될 수 있습니다.
Pryftan

예, 이것은 인수가없는 긴 인수 이름을위한 poc입니다 .getops와 같은 일종의 구성이 필요한 두 가지를 구분합니다. 그리고 교대와 관련하여 항상 세트로 "다시 놓을"수 있습니다. 어쨌든 매개 변수가 예상되는 경우 구성 가능해야합니다. 마법을 사용할 수도 있지만 마법 정지와 위치 매개 변수가 시작되었다는 신호를 보내기 위해 사용자가 사용하도록 강요합니다.
estani

그럴 수 있지. 그것은 합리적입니다. Tbh 나는 내가 무슨 일을했는지조차 기억하지 못하고이 질문 자체에 대해 완전히 잊어 버렸습니다. 내가 어떻게 찾았는지 모호하게 기억합니다. 건배. 아, 아이디어에 +1이 있습니다. 당신은 노력을 겪었고 당신은 또한 당신이 무엇을 얻고 있는지 명확히했습니다. 나는 다른 사람들에게 아이디어를주기 위해 노력하는 사람들을 존중합니다.
Pryftan

0

내장getopts 은 짧은 옵션 만 구문 분석하지만 (ksh93 제외) getopts가 긴 옵션을 처리하도록 몇 줄의 스크립팅을 추가 할 수 있습니다.

다음은 http://www.uxora.com/unix/shell-script/22-handle-long-options-with-getopts 에있는 코드의 일부입니다 .

  #== set short options ==#
SCRIPT_OPTS=':fbF:B:-:h'
  #== set long options associated with short one ==#
typeset -A ARRAY_OPTS
ARRAY_OPTS=(
    [foo]=f
    [bar]=b
    [foobar]=F
    [barfoo]=B
    [help]=h
    [man]=h
)

  #== parse options ==#
while getopts ${SCRIPT_OPTS} OPTION ; do
    #== translate long options to short ==#
    if [[ "x$OPTION" == "x-" ]]; then
        LONG_OPTION=$OPTARG
        LONG_OPTARG=$(echo $LONG_OPTION | grep "=" | cut -d'=' -f2)
        LONG_OPTIND=-1
        [[ "x$LONG_OPTARG" = "x" ]] && LONG_OPTIND=$OPTIND || LONG_OPTION=$(echo $OPTARG | cut -d'=' -f1)
        [[ $LONG_OPTIND -ne -1 ]] && eval LONG_OPTARG="\$$LONG_OPTIND"
        OPTION=${ARRAY_OPTS[$LONG_OPTION]}
        [[ "x$OPTION" = "x" ]] &&  OPTION="?" OPTARG="-$LONG_OPTION"

        if [[ $( echo "${SCRIPT_OPTS}" | grep -c "${OPTION}:" ) -eq 1 ]]; then
            if [[ "x${LONG_OPTARG}" = "x" ]] || [[ "${LONG_OPTARG}" = -* ]]; then 
                OPTION=":" OPTARG="-$LONG_OPTION"
            else
                OPTARG="$LONG_OPTARG";
                if [[ $LONG_OPTIND -ne -1 ]]; then
                    [[ $OPTIND -le $Optnum ]] && OPTIND=$(( $OPTIND+1 ))
                    shift $OPTIND
                    OPTIND=1
                fi
            fi
        fi
    fi

    #== options follow by another option instead of argument ==#
    if [[ "x${OPTION}" != "x:" ]] && [[ "x${OPTION}" != "x?" ]] && [[ "${OPTARG}" = -* ]]; then 
        OPTARG="$OPTION" OPTION=":"
    fi

    #== manage options ==#
    case "$OPTION" in
        f  ) foo=1 bar=0                    ;;
        b  ) foo=0 bar=1                    ;;
        B  ) barfoo=${OPTARG}               ;;
        F  ) foobar=1 && foobar_name=${OPTARG} ;;
        h ) usagefull && exit 0 ;;
        : ) echo "${SCRIPT_NAME}: -$OPTARG: option requires an argument" >&2 && usage >&2 && exit 99 ;;
        ? ) echo "${SCRIPT_NAME}: -$OPTARG: unknown option" >&2 && usage >&2 && exit 99 ;;
    esac
done
shift $((${OPTIND} - 1))

테스트는 다음과 같습니다.

# Short options test
$ ./foobar_any_getopts.sh -bF "Hello world" -B 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello world
files=file1 file2

# Long and short options test
$ ./foobar_any_getopts.sh --bar -F Hello --barfoo 6 file1 file2
foo=0 bar=1
barfoo=6
foobar=1 foobar_name=Hello
files=file1 file2

그렇지 않으면 최근 Korn Shell ksh93에서 getopts긴 옵션을 자연스럽게 구문 분석하고 매뉴얼 페이지를 모두 표시 할 수 있습니다. ( http://www.uxora.com/unix/shell-script/20-getopts-with-man-page-and-long-options 참조 )


0

내장 OS X (BSD) getopt는 긴 옵션을 지원하지 않지만 GNU 버전은 다음을 지원 brew install gnu-getopt합니다. 그런 다음, 비슷한 : cp /usr/local/Cellar/gnu-getopt/1.1.6/bin/getopt /usr/local/bin/gnu-getopt.


0

EasyOptions 는 짧고 긴 옵션을 처리합니다.

## Options:
##   --verbose, -v   Verbose mode
##   --logfile=NAME  Log filename

source easyoptions || exit

if test -n "${verbose}"; then
    echo "log file: ${logfile}"
    echo "arguments: ${arguments[@]}"
fi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.