getopts로 긴 옵션 처리

getopts로 긴 옵션 처리

옵션을 구문 분석하고 있지만 getopts긴 옵션도 처리하고 싶습니다.

print-args ()
{
 title="$1" ; shift
 printf "\n%s\n" "${title}: \$@:"
 for arg in "$@"; do
   (( i = i + 1 ))
   printf "%s |%s|\n" "${i}." "$arg"
 done
}

getopts_test ()
{
 aggr=()
 for arg in "$@"; do
   case $arg in
    ("--colour"|"--color")     aggr+=( "-c" ) ;;
    ("--colour="*|"--color="*) aggr+=( "-c" "${arg#*=}" ) ;;
    (*)  aggr+=( "$arg" ) ;;
   esac
 done

 print-args "print" "$@"

 eval set -- "${aggr[@]}"
 print-args "eval" "$@"

 set -- "${aggr[@]}"
 print-args "set" "$@"

 local OPTIND OPTARG
 local shortopts="C:"
 while getopts "$shortopts" arg; do
   case $arg in
    ("c") context="$OPTARG" ;;
    (*) break ;;
   esac
 done
 shift $(( OPTIND - 1 ))
}

그런데 의 사용이 set -- "${aggr[@]}"맞는지 궁금합니다.

아니면 다음 ( 을 사용하여 eval)이 더 적합합니까?

eval set -- "${aggr[@]}"

아래에 표시된 테스트를 수행했습니다. eval을 사용하면 문자열 "Gunga Din"이 분할되는 반면 eval을 사용하면 set -- "${aggr[@]}"단일 문자열로 올바르게 구문 분석됩니다.

getopts_test -f -g 130 --colour="170 20" "Gunga Din"

print: $@:
1. |-f|
2. |-g|
3. |130|
4. |--colour=170 20|
5. |Gunga Din|

eval: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170|
6. |20|
7. |Gunga|
8. |Din|

set: $@:
1. |-f|
2. |-g|
3. |130|
4. |-c|
5. |170 20|
6. |Gunga Din|

그런 다음 GNU가 아닌 getopt.

getopt_test ()
{
 shortopts="Vuhv::H::w::e::n::l::C:"
 shortopts="${shortopts}bgcrmo"
 longopts="version,usage,help,verbosity::"
 longopts="${longopts},heading::,warning::,error::"
 longopts="${longopts},blu,grn,cyn,red,mgn,org"
 
 opts=$( getopt -o "$shortopts" -l "$longopts" -n "${0##*/}" -- "$@" )

 print-args "\$@:" "$@"
 print-args "opts:" "$opts"

 set -- "$opts"
 print-args "set -- \"$opts\"" "$@"

 eval set -- "$opts"
 print-args "eval set -- \"$opts\"" "$@"

}

이로 인해 다음과 같은 결과가 발생했습니다.

getopt_test --warning=3 "foo'bar" "Gunga Din"

$@:
1. |--warning=3|
2. |foo'bar|
3. |Gunga Din|

opts:
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|

set -- "$opts"
1. | --warning '3' -- 'foo'\''bar' 'Gunga Din'|

eval set -- "$opts"
1. |--warning|
2. |3|
3. |--|
4. |foo'bar|
5. |Gunga Din|

표시된 대로 getopt의 결과는 위치 인수가 재배열된 단일 항목입니다. 이는 옵션 구문 분석 및 처리를 위해 문자열 eval set -- "$opts"의 위치 인수를 opts5개 항목으로 분할하는 데 사용해야 함을 보여줍니다 .

답변1

--context인수를 전처리하고 각 인수를 -C변경 하여 getopts처리할 수 있다는 아이디어가 있습니까 ? 나는 그것이 작동할 것이라고 생각하지만, GNU 스타일의 긴 옵션도 형식의 인수를 취할 수 --context=foobar있으며 여기의 구성은 이를 지원하지 않는다는 점에 유의하십시오. 사용자는 여기에서 이 특정 도구에 두 개의 서로 다른 인수가 필요하다는 것을 알아야 합니다 --context foobar. 아니면 전처리를 더 복잡하게 만들어야 합니다.

로 시작하는 모든 인수를 확인하고 싶을 수도 있습니다. --그렇지 않으면 잘못 입력한 인수는 그대로 --cotnext적용되어 알 수 없는 옵션에 대한 불만 사항이 표시됩니다. getopts(또는 더 나쁜 경우 잘못된 옵션이 활성화됩니다.)

그런데 의 사용이 set -- "${aggr[@]}"맞는지 궁금합니다.

아니면 다음(eval 사용)이 더 적합합니까?

set -- "${aggr[@]}"배열의 요소를 고유한 단어로 확장한 다음 해당 단어를 위치 매개변수에 할당합니다. 각 배열 요소는 변경 없이 정확히 하나의 위치 매개변수가 됩니다.

eval set -- "${aggr[@]}"배열의 모든 요소를 ​​확장한 다음 공백으로 결합하고 앞에 추가한 set --다음 결과를 쉘 명령으로 평가합니다. 즉, 배열 요소 abc def, $(date >&2), 가 있는 경우 ghi'jkl명령은 다음과 같습니다.

set -- abc def $(date >&2) ghi'jkl 

abc이는 두 개의 개별 매개변수로 및 로 끝나고 def단일 작은따옴표로 인해 구문 오류가 발생한다는 점을 제외하고 날짜를 stderr에 인쇄합니다.

eval쉘 입력에 대해 인용된 출력을 생성하도록 설계된 것이 있는 경우 사용하는 것이 적절할 것입니다.


Linux를 사용하고 있고 이식성에 관심이 없다면 roaima가 의견에서 제안한 것을 수행하고 util-linux 버전 getopt( s)을 사용할 수 있습니다. 긴 옵션도 지원합니다. 사용 방법을 보여주는 답변이 있습니다.getopt, getopts 또는 수동 구문 분석 - 짧은 옵션과 긴 옵션을 모두 지원하려면 무엇을 사용해야 합니까?그리고이 SO 답변그리고 또한내 대답은 여기.

그런데 그걸로 getopt당신은~일 것이다명령 eval으로서는 배열과 같은 목록이 아닌 단일 문자열을 출력으로 생성하는 것으로 제한되므로 셸 인용을 사용하여 문제를 해결합니다.

답변2

optstring에 인수를 사용하는 짧은 옵션을 추가하고 에서 실제 긴 옵션을 검색하여 내장된 --foo스타일의 긴 옵션을 구문 분석할 수 있습니다 . 간단한 예:getopts-$OPTARG

while getopts :sc:-: o; do
    case $o in
    :) echo >&2 "option -$OPTARG needs an argument"; continue;;
    '?') echo >&2 "unknown option -$OPTARG"; continue;;
    -) o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; OPTARG=${OPTARG#=};;
    esac
    echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"

그런 다음 또는 로 사용할 수 script -c foo있습니다 script --context=foo.

짧은 옵션과 마찬가지로 긴 옵션도 검증하고 축약된 형식도 허용하려면 좀 더 복잡한 것이 필요합니다. 그런 형편없는 쉘 스크립트를 과도하게 엔지니어링하는 것은 별로 지혜롭지 않지만, 예를 원한다면 다음과 같습니다:

short_opts=sc:
long_opts=silent/ch/context:/check/co   # those who take an arg END with :

# override via command line for testing purposes
# if [ "$#" -ge 2 ]; then
#   short_opts=$1; long_opts=$2; shift 2
# fi

while getopts ":$short_opts-:" o; do
    case $o in
    :) echo >&2 "option -$OPTARG needs an argument" ;continue;;
    '?') echo >&2 "bad option -$OPTARG" ;continue;;
    -)  o=${OPTARG%%=*}; OPTARG=${OPTARG#"$o"}; lo=/$long_opts/
        case $lo in
        *"/$o"[!/:]*"/$o"[!/:]*) echo >&2 "ambiguous option --$o"; continue;;
        *"/$o"[:/]*) ;;
        *) o=$o${lo#*"/$o"}; o=${o%%[/:]*} ;;
        esac
        case $lo in
        *"/$o/"*) OPTARG= ;;
        *"/$o:/"*)
            case $OPTARG in
            '='*)   OPTARG=${OPTARG#=};;
            *)  eval "OPTARG=\$$OPTIND"
                if [ "$OPTIND" -le "$#" ] && [ "$OPTARG" != -- ]; then
                    OPTIND=$((OPTIND + 1))
                else
                    echo >&2 "option --$o needs an argument"; continue
                fi;;
            esac;;
        *) echo >&2 "unknown option --$o"; continue;;
        esac
    esac
    echo "OPT $o=$OPTARG"
done
shift "$((OPTIND - 1))"
echo "ARGS $*"

그 다음에

$ ./script --context=33
OPT context=33
$ ./script --con=33
OPT context=33
$ ./script --co
OPT co=
$ ./script --context
option --context needs an argument

관련 정보