2584 votes

Comment analyser les arguments de la ligne de commande dans Bash ?

Disons que j'ai un script qui est appelé avec cette ligne :

./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile

ou celui-ci :

./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile 

Quelle est la manière acceptée d'analyser ceci de telle sorte que dans chaque cas (ou une combinaison des deux) $v , $f et $d seront toutes réglées sur true y $outFile sera égal à /fizz/someOtherFile ?

2 votes

Pour les utilisateurs de zsh, il existe un excellent utilitaire appelé zparseopts qui peut le faire : zparseopts -D -E -M -- d=debug -debug=d Et avoir les deux -d y --debug dans le $debug tableau echo $+debug[1] retournera 0 ou 1 si l'un d'entre eux est utilisé. Réf : zsh.org/mla/utilisateurs/2011/msg00350.html

4 votes

Très bon tutoriel : linuxcommand.org/lc3_wss0120.php . J'aime particulièrement l'exemple "Options de la ligne de commande".

0 votes

J'ai créé un script qui le fait pour vous, il s'appelle - github.com/unfor19/bargs

3622voto

Richard Bronosky Points 3163

Séparés par des espaces (par exemple, --option argument )

cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    -e|--extension)
      EXTENSION="$2"
      shift # past argument
      shift # past value
      ;;
    -s|--searchpath)
      SEARCHPATH="$2"
      shift # past argument
      shift # past value
      ;;
    --default)
      DEFAULT=YES
      shift # past argument
      ;;
    -*|--*)
      echo "Unknown option $1"
      exit 1
      ;;
    *)
      POSITIONAL_ARGS+=("$1") # save positional arg
      shift # past argument
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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 /etc/hosts
Résultat du copier-coller du bloc ci-dessus
FILE EXTENSION  = conf
SEARCH PATH     = /etc
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
Utilisation
demo-space-separated.sh -e conf -s /etc /etc/hosts

Bash Equals-Separated (par exemple, --option=argument )

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
      ;;
    --default)
      DEFAULT=YES
      shift # past argument with no value
      ;;
    -*|--*)
      echo "Unknown option $i"
      exit 1
      ;;
    *)
      ;;
  esac
done

echo "FILE EXTENSION  = ${EXTENSION}"
echo "SEARCH PATH     = ${SEARCHPATH}"
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 /etc/hosts
Résultat du copier-coller du bloc ci-dessus
FILE EXTENSION  = conf
SEARCH PATH     = /etc
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
Utilisation
demo-equals-separated.sh -e=conf -s=/etc /etc/hosts

Pour mieux comprendre ${i#*=} rechercher "Substring Removal" dans ce guide . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"` qui appelle un sous-processus inutile ou `echo "$i" | sed 's/[^=]*=//'` qui appelle deux des sous-processus inutiles.


Utiliser bash avec getopt[s]

Les limitations de getopt(1) (plus anciennes, relativement récentes) getopt ) :

  • ne peut pas gérer les arguments qui sont des chaînes vides
  • ne peut pas gérer les arguments avec des espaces blancs incorporés

Plus récent getopt n'ont pas ces limitations. Pour plus d'informations, consultez ces docs .


POSIX getopts

En outre, le shell POSIX et d'autres offrent getopts qui n'a pas ces limitations. J'ai inclus un exemple simpliste de getopts exemple.

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
Résultat du copier-coller du bloc ci-dessus
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Utilisation
demo-getopts.sh -vf /etc/hosts foo bar

Les avantages de getopts sont :

  1. Il est plus portable, et fonctionnera dans d'autres coquilles comme dash .
  2. Il peut gérer plusieurs options uniques comme -vf filename de la manière typique d'Unix, automatiquement.

L'inconvénient de getopts est qu'il ne peut traiter que les options courtes ( -h pas --help ) sans code supplémentaire.

Il existe un tutoriel getopts qui explique la signification de toutes les syntaxes et variables. Dans bash, il y a aussi help getopts ce qui pourrait être instructif.

61 votes

Est-ce vraiment vrai ? D'après Wikipedia il y a une nouvelle version améliorée de GNU de getopt qui comprend toutes les fonctionnalités de getopts et même plus. man getopt sur Ubuntu 13.04 sorties getopt - parse command options (enhanced) comme nom, donc je présume que cette version améliorée est maintenant standard.

59 votes

Le fait qu'une chose soit d'une certaine manière sur votre système est une prémisse très faible sur laquelle on peut baser des suppositions de "standard".

18 votes

@Livven, que getopt n'est pas un utilitaire GNU, il fait partie de l'application util-linux .

165voto

guneysus Points 485

De digitalpeer.com avec des modifications mineures :

Utilisation 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}

Pour mieux comprendre ${i#*=} rechercher "Substring Removal" dans ce guide . Il est fonctionnellement équivalent à `sed 's/[^=]*=//' <<< "$i"` qui appelle un sous-processus inutile ou `echo "$i" | sed 's/[^=]*=//'` qui appelle deux des sous-processus inutiles.

5 votes

Neat ! Bien que cela ne fonctionnera pas pour les arguments séparés par des espaces à la mount -t tempfs ... . On peut probablement résoudre ce problème en utilisant quelque chose comme while [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;; etc.

4 votes

Cela ne peut pas supporter -vfd style options courtes combinées.

1 votes

Si vous voulez évaluer de manière générique --option y -option sans répéter OPTION=$i à chaque fois, utilisez -*=*) comme modèle de correspondance et eval ${i##*-} .

113voto

Matt J Points 15475

getopt() / getopts() est une bonne option. Copié de aquí :

L'utilisation simple de "getopt" est montrée dans ce mini-script :

#!/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

Ce que nous avons dit, c'est que n'importe quel élément de -a, -b, -c ou -d sera autorisé, mais que -c est suivi d'un argument (le "c :" le dit).

Si on appelle ça "g" et qu'on l'essaie :

bash-2.05a$ ./g -abc foo
Before getopt
-abc
foo
After getopt
-->-a
-->-b
-->-c
-->foo
-->--

Nous commençons avec deux arguments, et "getopt" sépare les options et et les place dans leur propre argument. Il a aussi ajouté "--".

6 votes

Utilisation de $* est un usage brisé de getopt . (Il hostile les arguments avec des espaces.) Voir ma réponse pour une utilisation correcte.

0 votes

Pourquoi voulez-vous le rendre plus compliqué ?

0 votes

@Matt J, la première partie du script (pour i) serait capable de gérer les arguments contenant des espaces si vous utilisez "$i" au lieu de $i. Le getopts ne semble pas être capable de gérer les arguments avec des espaces. Quel serait l'avantage d'utiliser getopt par rapport à la boucle for i ?

44voto

Shane Day Points 21

J'ai utilisé les réponses précédentes comme point de départ pour mettre de l'ordre dans mon ancienne analyse paramétrique adhoc. J'ai ensuite remanié le code du modèle suivant. Il gère à la fois les paramètres longs et courts, en utilisant des arguments séparés par des = ou des espaces, ainsi que de multiples paramètres courts regroupés. Enfin, il ré-insère tous les arguments non-param dans les variables $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

0 votes

Ce code ne peut pas gérer les options avec des arguments comme celui-ci : -c1 . Et l'utilisation de = de séparer les options courtes de leurs arguments est inhabituel...

2 votes

J'ai rencontré deux problèmes avec cet utile morceau de code : 1) le "shift" dans le cas de "-c=foo" finit par manger le paramètre suivant ; et 2) "c" ne devrait pas être inclus dans le motif "[cfr]" pour les options courtes combinables.

17voto

Alek Points 81

Je pense que celui-ci est assez simple à utiliser :

#!/bin/bash
#

readopt='getopts $opts opt;rc=$?;[ "$rc$opt" = "0?" ]&&exit 1;[ $rc = 0 ]||{ shift $[OPTIND-1];false; }'

opts=vfdo:

# Enumerating options
while eval "$readopt"
do
    echo OPT:$opt ${OPTARG+OPTARG:$OPTARG}
done

# Enumerating arguments
for arg
do
    echo ARG:$arg
done

Exemple d'invocation :

./myscript -v -do /fizz/someOtherFile -f ./foo/bar/someFile
OPT:v 
OPT:d 
OPT:o OPTARG:/fizz/someOtherFile
OPT:f 
ARG:./foo/bar/someFile

1 votes

Je les ai tous lus et celui-ci est mon préféré. Je n'aime pas utiliser -a=1 comme style argc. Je préfère mettre d'abord l'option principale -options et ensuite les options spéciales avec un espacement simple. -o option . Je cherche la façon la plus simple et la meilleure de lire les argvs.

1 votes

Cela fonctionne très bien mais si vous passez un argument à une option non a :, toutes les options suivantes seront prises comme arguments. Vous pouvez vérifier cette ligne ./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile avec votre propre script. L'option -d n'est pas définie comme d :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X