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

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

./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 : 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유익한 정보가 있습니다.


답변

향상된 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 년까지만 추적했습니다)


답변

간결한 방법

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


답변

에서 : 약간 수정 한 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/[^=]*=//'`


답변

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”는 옵션을 분리하고 각각의 인수에 넣습니다. “-“도 추가되었습니다.


답변

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

  • 손잡이 -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 년 늦었지만 포기하고 싶습니다. 이전 답변을 이전의 임시 매개 변수 구문 분석을 정리하기위한 출발점으로 사용했습니다. 그런 다음 다음 템플릿 코드를 리팩터링했습니다. = 또는 공백으로 구분 된 인수와 함께 여러 개의 짧은 매개 변수를 함께 사용하여 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