web-dev-qa-db-fra.com

Comment utiliser des options longues avec le composant intégré Bash getopts?

J'essaie d'analyser une option -temp avec Bash getopts. J'appelle mon script comme ceci:

./myscript -temp /foo/bar/someFile

Voici le code que j'utilise pour analyser les options.

while getopts "temp:shots:o:" option; do
    case $option in
        temp) TMPDIR="$OPTARG" ;;
        shots) NUMSHOTS="$OPTARG" ;;
        o) OUTFILE="$OPTARG" ;;
        *) usage ;;
    esac
done
shift $(($OPTIND - 1))

[ $# -lt 1 ] && usage
32
Hemang

getopts ne peut analyser que des options courtes. 

La plupart des systèmes ont également une commande externe getopt, mais getopt n'est pas standard et est généralement cassé par conception, car il ne peut pas gérer tous les arguments en toute sécurité (arguments avec espaces et arguments vides), seul GNU getopt peut les gérer en toute sécurité , mais seulement si vous l’utilisez de manière spécifique à GNU. 

Le choix le plus simple consiste à n'utiliser ni l'un ni l'autre, il suffit d'itérer les arguments du script avec une boucle while et de procéder à l'analyse syntaxique. 

Voir http://mywiki.wooledge.org/BashFAQ/035 pour un exemple.

39
geirha

Comme d'autres personnes l'ont expliqué, getopts n'analyse pas les options longues. Vous pouvez utiliser getopt, mais ce n'est pas portable (et c'est cassé sur une plate-forme ...)

Pour contourner le problème, vous pouvez implémenter une boucle Shell. Voici un exemple qui transforme les options longues en options courtes avant d'utiliser la commande standard getopts (c'est plus simple à mon avis):

# Transform long options to short ones
for arg in "$@"; do
  shift
  case "$arg" in
    "--help") set -- "$@" "-h" ;;
    "--rest") set -- "$@" "-r" ;;
    "--ws")   set -- "$@" "-w" ;;
    *)        set -- "$@" "$arg"
  esac
done

# Default behavior
rest=false; ws=false

# Parse short options
OPTIND=1
while getopts "hrw" opt
do
  case "$opt" in
    "h") print_usage; exit 0 ;;
    "r") rest=true ;;
    "w") ws=true ;;
    "?") print_usage >&2; exit 1 ;;
  esac
done
shift $(expr $OPTIND - 1) # remove options from positional parameters
42
mcoolive

getopts est utilisé par les procédures Shell pour analyser les paramètres de position d'un caractère uniquement .__ (pas d'options longues de style GNU (--myoption) ni d'options longues de style XF86 (-myoption))

2
Stephane Rouberol

Il est vrai que builtin bash getopts n'analyse que les options courtes, .__, mais vous pouvez toujours ajouter quelques lignes de script pour que getopts gère les options longues.

Voici une partie du code trouvé dans http://www.uxora.com/unix/Shell-script/22-handle-long-options-with-getopts

  #== set options ==#
SCRIPT_OPTS=':fbF:B:-:h'
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))

Voici un test:

# 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

Sinon, dans Korn Shell ksh93, getopts peut analyser naturellement de longues options et même afficher une page de manuel de la même manière. (voir http://www.uxora.com/unix/Shell-script/20-getopts-with-man-page-and-long-options )

Michel VONGVILAY.

Vous ne pouvez pas utiliser la commande intégrée getopts Bash pour de longues options, du moins sans avoir à créer vos propres fonctions d'analyse. Vous devriez plutôt jeter un œil au binaire //usr/bin/getopt (fourni sur mon système par le paquet util-linux ; votre kilométrage peut varier).

Voir getopt (1) pour des options d'appel spécifiques.

0
Todd A. Jacobs

grâce à @mcoolive.

J'ai pu utiliser votre idée $ @ pour convertir des options Word complètes et des options longues en options à une seule lettre. Je voulais signaler à quiconque utilisant cette idée que je devais également inclure shift $(expr $OPTIND - 1) avant d'exécuter les arguments via la boucle getopts.

But totalement différent mais cela fonctionne bien.

# convert long Word options to short Word for ease of use and portability

for argu in "$@"; do
  shift
  #echo "curr arg = $1"
  case "$argu" in
"-start"|"--start")
                   # param=param because no arg is required
                   set -- "$@" "-s"
                   ;;
"-pb"|"--pb"|"-personalbrokers"|"--personalbrokers")
                   # pb +arg required
                   set -- "$@" "-p $1"; #echo "arg=$argu"
                   ;;
"-stop"|"--stop")
                   # param=param because no arg is required 
                   set -- "$@" "-S" 
                   ;;
                #  the catch all option here removes all - symbols from an
                #  argument. if an option is attempted to be passed that is
                #  invalid, getopts knows what to do...
               *)  [[ $(echo $argu | grep -E "^-") ]] && set -- "$@" "${argu//-/}" || echo "no - symbol. not touching $argu" &>/dev/null
                   ;;
esac
done

#echo -e "\n final option conversions = $@\n"
# remove options from positional parameters for getopts parsing
shift $(expr $OPTIND - 1)

declare -i runscript=0
# only p requires an argument hence the p:
 while getopts "sSp:" param; do
[[ "$param" == "p" ]] && [[ $(echo $OPTARG | grep -E "^-") ]] && funcUsage "order" 
#echo $param
#echo "OPTIND=$OPTIND"
case $param in
s)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       arg0=start
       ;;
 p)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       [[ "$OPTARG" == "all" ]] && echo -e "argument \"$OPTARG\" accepted. continuing." && (( runscript += 1 )) || usage="open"
       [[ $( echo $pbString | grep -w "$OPTARG" ) ]] && echo -e "pb $OPTARG was validated. continuing.\n" && (( runscript += 1 )) || usage="personal"
       [[ "$runscript" -lt "1" ]] && funcUsage "$usage" "$OPTARG"
       arg0=start
       ;;
S)
       OPTARG=${OPTARG/ /}
       getoptsRan=1
       echo "$param was passed and this is it's arg $OPTARG"
       arg0=stop
       ;;
*)
       getoptsRan=1
       funcUsage
       echo -e "Invalid argument\n"
       ;;
esac
done
funcBuildExcludes "$@"
shift $((OPTIND-1))
0
Benjamin West

Un bricolage simple si vous rencontrez des difficultés pour utiliser la variable getopts intégrée:

Utilisation:

$ ./test-args.sh --a1 a1 --a2 "a 2" --a3 --a4= --a5=a5 --a6="a 6"
a1 = "a1"
a2 = "a 2"
a3 = "TRUE"
a4 = ""
a5 = "a5"
a6 = "a 6"
a7 = ""

Scénario:

#!/bin/bash

function main() {
    ARGS=`getArgs "$@"`

    a1=`echo "$ARGS" | getNamedArg a1`
    a2=`echo "$ARGS" | getNamedArg a2`
    a3=`echo "$ARGS" | getNamedArg a3`
    a4=`echo "$ARGS" | getNamedArg a4`
    a5=`echo "$ARGS" | getNamedArg a5`
    a6=`echo "$ARGS" | getNamedArg a6`
    a7=`echo "$ARGS" | getNamedArg a7`

    echo "a1 = \"$a1\""
    echo "a2 = \"$a2\""
    echo "a3 = \"$a3\""
    echo "a4 = \"$a4\""
    echo "a5 = \"$a5\""
    echo "a6 = \"$a6\""
    echo "a7 = \"$a7\""

    exit 0
}


function getArgs() {
    for arg in "$@"; do
        echo "$arg"
    done
}


function getNamedArg() {
    ARG_NAME=$1

    sed --regexp-extended --quiet --expression="
        s/^--$ARG_NAME=(.*)\$/\1/p  # Get arguments in format '--arg=value': [s]ubstitute '--arg=value' by 'value', and [p]rint
        /^--$ARG_NAME\$/ {          # Get arguments in format '--arg value' ou '--arg'
            n                       # - [n]ext, because in this format, if value exists, it will be the next argument
            /^--/! p                # - If next doesn't starts with '--', it is the value of the actual argument
            /^--/ {                 # - If next do starts with '--', it is the next argument and the actual argument is a boolean one
                # Then just repla[c]ed by TRUE
                c TRUE
            }
        }
    "
}


main "$@"
0
lmcarreiro