Donner à un script bash l'option d'accepter des indicateurs, comme une commande [duplicate]

Oct 08 2020

J'écris des scripts bash simples et j'aimerais qu'il accepte les paramètres de la ligne de commande dans n'importe quel ordre.

J'ai parcouru le Web et écrit une fonction simple avec une déclaration de cas dans une boucle while. À l'heure actuelle, la partie «toute commande» fonctionne - mais elle ne prend que le premier paramètre que j'ai défini. Je fais certainement quelque chose de mal, mais le script est assez nouveau pour moi et je n'avais pas été en mesure de le comprendre - votre aide serait grandement appréciée. La partie flags du script est la suivante:

    #Parameters - source,destination,credentials,bandwidth,timeout,port,help
flags () {
        while test $# -gt 0; do case "$1" in
                        -s|--source)
                                shift
                                if test $# -gt 0; then export SOURCE=$1
                                else
                                        echo "No source directory specified!"
                                        exit 1
                                fi
                                ;;
                        -d|--destination)
                                shift
                                if test $# -gt 0; then export DESTINATION=$1
                                fi
                                ;;
                        -c|--credentials)
                                shift
                                if test $# -gt 0; then export CREDENTIALS=$1
                                fi
                                ;;
                        -b|--bandwidth)
                                shift
                                if test $# -gt 0; then export BANDWIDTH=$1
                                fi
                                ;;
                        -t|--timeout)
                                shift
                                if test $# -gt 0; then export TIMEOUT=$1
                                fi
                                ;;
                        -p|--port)
                                shift
                                if test $# -gt 0; then export PORT=$1
                                fi
                                ;;
                        -h|--help)
                                shift
                                if test $# -gt 0; then echo "Help goes here" fi ;; -l|--compression-level) shift if test $# -gt 0; then
                                        export COMPRESS_LEVEL=$1 fi ;; *) break ;; esac done } flags "$@"
echo "source is $SOURCE, destination is $DESTINATION, credentials are $CREDENTIALS, bandwidth is $BANDWIDTH, timeout is $TIMEOUT, port is $PORT"

Idéalement, certains de ces paramètres seraient obligatoires et d'autres facultatifs, mais ce n'est pas obligatoire.

Comment puis-je corriger ce script pour accepter l'un de ces paramètres (à la fois les formes longues et courtes, idéalement) dans n'importe quel ordre?

Merci!

Réponses

JonathanLeffler Oct 08 2020 at 07:38

Comme indiqué dans les commentaires, après avoir utilisé l'argument (par exemple pour les informations d'identification), vous avez besoin d'un autre quart de travail. Vous devez être cohérent dans votre rapport d'erreurs pour les arguments inexistants. Si vous obtenez -hou --help, vous devez simplement imprimer l'aide et quitter; vous ne devriez pas tester plus d'arguments. Si de l'aide est demandée, vous la donnez et ne faites rien d'autre. Vous devez également l' écho des erreurs d'erreur standard: echo "message" >&2. Vos messages doivent être précédés du nom du script / programme: et etc.arg0=$(basename "$0" .sh)echo "$arg0: message" >&2

En rassemblant les modifications, vous pourriez créer un script comme celui-ci:

#!/bin/sh

arg0=$(basename "$0" .sh) blnk=$(echo "$arg0" | sed 's/./ /g') usage_info() { echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \\"
    echo "       $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \\" echo " $blnk [{-t|--timeout} timeout] [{-p|--port} port] \\"
    echo "       $blnk [-h|--help] [{-l|--compression-level} level]" } usage() { exec 1>2 # Send standard output to standard error usage_info exit 1 } error() { echo "$arg0: $*" >&2 exit 1 } help() { usage_info echo echo " {-s|--source} source -- Set source directory (default: .)" echo " {-d|--destination} destination -- Set destination" echo " {-c|--credentials} credentials -- Set credentials" echo " {-b|--bandwidth} bandwidth -- Set maximum bandwidth" echo " {-t|--timeout} timeout -- Set timeout (default: 60s)" echo " {-p|--port} port -- Set port number (default: 1234)" echo " {-l|--compression-level} level -- Set compression level (default: 1)" echo " {-h|--help} -- Print this help message and exit" # echo " {-V|--version} -- Print version information and exit" exit 0 } flags() { while test $# -gt 0
    do
        case "$1" in (-s|--source) shift [ $# = 0 ] && error "No source directory specified"
            export SOURCE="$1" shift;; (-d|--destination) shift [ $# = 0 ] && error "No destination specified"
            export DESTINATION="$1" shift;; (-c|--credentials) shift [ $# = 0 ] && error "No credentials specified"
            export CREDENTIALS="$1" shift;; (-b|--bandwidth) shift [ $# = 0 ] && error "No bandwidth specified"
            export BANDWIDTH="$1" shift;; (-t|--timeout) shift [ $# = 0 ] && error "No timeout specified"
            export TIMEOUT="$1" shift;; (-p|--port) shift [ $# = 0 ] && error "No port specified"
            export PORT="$1" shift;; (-l|--compression-level) shift [ $# = 0 ] && error "No compression level specified"
            export COMPRESS_LEVEL="$1" shift;; (-h|--help) help;; # (-V|--version) # version_info;; (*) usage;; esac done } flags "$@"

echo "source is $SOURCE" echo "destination is $DESTINATION"
echo "credentials are $CREDENTIALS" echo "bandwidth is $BANDWIDTH"
echo "timeout is $TIMEOUT" echo "port is $PORT"

Exemple d'exécution (nom du script:) flags53.sh:

$ sh flags53.sh -c XYZ -d PQR -s 123 -l 4 -t 99 -b 12 -p 56789 source is 123 destination is PQR credentials are XYZ bandwidth is 12 timeout is 99 port is 56789 $ sh flags53.sh -c XYZ --destination PQR -s 123 -l 4 --timeout 99 -b 12 --port 56789
source is 123
destination is PQR
credentials are XYZ
bandwidth is 12
timeout is 99
port is 56789
$ sh flags53.sh -c XYZ -h Usage: flags53 [{-s|--source} source] [{-d|--destination} destination] \ [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \ [{-t|--timeout} timeout] [{-p|--port} port] \ [-h|--help] [{-l|--compression-level} level] {-s|--source} source -- Set source directory (default: .) {-d|--destination} destination -- Set destination {-c|--credentials} credentials -- Set credentials {-b|--bandwidth} bandwidth -- Set maximum bandwidth {-t|--timeout} timeout -- Set timeout (default: 60s) {-p|--port} port -- Set port number (default: 1234) {-l|--compression-level} level -- Set compression level (default: 1) {-h|--help} -- Print this help message and exit $

Notez que l'aide demandée peut aller à la sortie standard au lieu de l'erreur standard, bien que l'envoi de l'aide à l'erreur standard ne soit pas un crime flagrant. L'aide obtient le message d'utilisation et des informations supplémentaires sur la signification de chaque option. Noter les valeurs par défaut (et les définir) est également une bonne idée. Il peut ne pas être nécessaire d'exporter les paramètres - vous pouvez simplement définir les variables sans explicite export. Vous devez vraiment définir les variables à leurs valeurs par défaut avant d'appeler la flagsfonction, ou au début de la flagsfonction. Cela évite d'hériter accidentellement des valeurs exportées (variables d'environnement). À moins, bien sûr, que vous ne vouliez accepter des variables d'environnement, mais alors vos noms devraient probablement recevoir un préfixe systématique approprié pour le nom du script. La plupart des programmes devraient avoir une option --versionou -V(à utiliser -vpour «verbose», pas pour la version). Si la commande n'accepte aucun argument sans option (nom de fichier), ajoutez une coche après la boucle d'analyse et vous plaignez des arguments indésirables. Si la commande doit avoir au moins un argument sans option, vérifiez-le à la place. Ne signalez pas une erreur lors de la réception --en argument; terminer la boucle de vérification et traiter tous les arguments restants comme des arguments sans option.

Un problème résiduel - les changements dans la fonction affectent la liste d'arguments de la fonction, pas le global "$@". You'd have to work out how to deal with that from this skeleton. I think I'd probably create an analogue to $OPTIND qui indique le nombre d'arguments à déplacer pour accéder aux arguments sans option. Le code de la fonction flags doit garder une trace du nombre d'arguments qu'il change.

Cela conduit au code révisé:

#!/bin/sh

arg0=$(basename "$0" .sh)
blnk=$(echo "$arg0" | sed 's/./ /g')

usage_info()
{
    echo "Usage: $arg0 [{-s|--source} source] [{-d|--destination} destination] \\" echo " $blnk [{-c|--credentials} credentials] [{-b|--bandwidth} bandwidth] \\"
    echo "       $blnk [{-t|--timeout} timeout] [{-p|--port} port] \\" echo " $blnk [-h|--help] [{-l|--compression-level} level]"
}

usage()
{
    exec 1>2   # Send standard output to standard error
    usage_info
    exit 1
}

error()
{
    echo "$arg0: $*" >&2
    exit 1
}

help()
{
    usage_info
    echo
    echo "  {-s|--source} source            -- Set source directory (default: .)"
    echo "  {-d|--destination} destination  -- Set destination"
    echo "  {-c|--credentials} credentials  -- Set credentials"
    echo "  {-b|--bandwidth} bandwidth      -- Set maximum bandwidth"
    echo "  {-t|--timeout} timeout          -- Set timeout (default: 60s)"
    echo "  {-p|--port} port                -- Set port number (default: 1234)"
    echo "  {-l|--compression-level} level  -- Set compression level (default: 1)"
    echo "  {-h|--help}                     -- Print this help message and exit"
#   echo "  {-V|--version}                  -- Print version information and exit"
    exit 0
}

flags()
{
    OPTCOUNT=0
    while test $# -gt 0 do case "$1" in
        (-s|--source)
            shift
            [ $# = 0 ] && error "No source directory specified" export SOURCE="$1"
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-d|--destination)
            shift
            [ $# = 0 ] && error "No destination specified" export DESTINATION=$1
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-c|--credentials)
            shift
            [ $# = 0 ] && error "No credentials specified" export CREDENTIALS=$1
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-b|--bandwidth)
            shift
            [ $# = 0 ] && error "No bandwidth specified" export BANDWIDTH=$1
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-t|--timeout)
            shift
            [ $# = 0 ] && error "No timeout specified" export TIMEOUT="$1"
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-p|--port)
            shift
            [ $# = 0 ] && error "No port specified" export PORT=$1
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-l|--compression-level)
            shift
            [ $# = 0 ] && error "No compression level specified" export COMPRESS_LEVEL="$1"
            shift
            OPTCOUNT=$(($OPTCOUNT + 2));;
        (-h|--help)
            help;;
#       (-V|--version)
#           version_info;;
        (--)
            shift
            OPTCOUNT=$(($OPTCOUNT + 1))
            break;;
        (*) usage;;
        esac
    done
    echo "DEBUG-1: [$*]" >&2 echo "OPTCOUNT=$OPTCOUNT" >&2
}

flags "$@" echo "DEBUG-2: [$*]" >&2
echo "OPTCOUNT=$OPTCOUNT" >&2 shift $OPTCOUNT
echo "DEBUG-3: [$*]" >&2 echo "source is $SOURCE"
echo "destination is $DESTINATION" echo "credentials are $CREDENTIALS"
echo "bandwidth is $BANDWIDTH" echo "timeout is $TIMEOUT"
echo "port is $PORT"

Il existe d'autres manières d'écrire l'arithmétique si vous souhaitez expérimenter. N'utilisez pas exprcependant.