Récupérer plusieurs arguments pour une seule option en utilisant getopts dans Bash

j'ai besoin d'aide getopts.

j'ai créé un script Bash qui ressemble à ceci quand il est lancé:

$ foo.sh -je env-d répertoire -s sous-répertoire -f fichier

cela fonctionne correctement lorsque vous manipulez un argument de chaque drapeau. Mais quand j'invoque plusieurs arguments de chaque drapeau, Je ne suis pas sûr de savoir comment extraire les informations des variables multiples dans getopts.

while getopts ":i:d:s:f:" opt
   do
     case $opt in
        i ) initial=$OPTARG;;
        d ) dir=$OPTARG;;
        s ) sub=$OPTARG;;
        f ) files=$OPTARG;;

     esac
done

après avoir saisi les options je veux alors construire le répertoire des structures à partir des variables

foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3

alors la structure du répertoire serait

/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3

des idées?

38
demandé sur codeforester 2011-09-23 17:39:02

8 réponses

Vous pouvez utilisez la même option plusieurs fois et ajoutez toutes les valeurs à un tableau.

Pour l'original question ici, de Ryan mkdir -p la solution est évidemment le meilleur.

Cependant, pour la question plus générale de obtenir de multiples valeurs de la même option avec getopts, elle est ici:

#!/bin/bash

while getopts "m:" opt; do
    case $opt in
        m) multi+=("$OPTARG");;
        #...
    esac
done
shift $((OPTIND -1))

echo "The first value of the array 'multi' is '$multi'"
echo "The whole list of values is '${multi[@]}'"

echo "Or:"

for val in "${multi[@]}"; do
    echo " - $val"
done

Le résultat:

$ /tmp/t
The first value of the array 'multi' is ''
The whole list of values is ''
Or:

$ /tmp/t -m "one arg with spaces"
The first value of the array 'multi' is 'one arg with spaces'
The whole list of values is 'one arg with spaces'
Or:
 - one arg with spaces

$ /tmp/t -m one -m "second argument" -m three
The first value of the array 'multi' is 'one'
The whole list of values is 'one second argument three'
Or:
 - one
 - second argument
 - three
54
répondu mivk 2015-12-02 13:46:38

je sais que cette question Est ancienne, mais je voulais y jeter cette réponse au cas où quelqu'un viendrait chercher une réponse.

les Shells comme BASH supportent la création récursive de répertoires comme celui-ci, donc un script n'est pas vraiment nécessaire. Par exemple, l'affiche originale voulait quelque chose comme:

$ foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
/test/directory/subdirectory/file1
/test/directory/subdirectory/file2
/test/directory/subdirectory/file3
/test/directory/subdirectory2/file1
/test/directory/subdirectory2/file2
/test/directory/subdirectory2/file3

cela se fait facilement avec cette ligne de commande:

pong:~/tmp
[10] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/{file1,file2,file3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’

Ou même un peu plus court:

pong:~/tmp
[12] rmclean$ mkdir -pv test/directory/{subdirectory,subdirectory2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory’
mkdir: created directory ‘test/directory/subdirectory/file1’
mkdir: created directory ‘test/directory/subdirectory/file2’
mkdir: created directory ‘test/directory/subdirectory/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’

ou plus courte, avec plus conformité:

pong:~/tmp
[14] rmclean$ mkdir -pv test/directory/subdirectory{1,2}/file{1,2,3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’

ou enfin, en utilisant des séquences:

pong:~/tmp
[16] rmclean$ mkdir -pv test/directory/subdirectory{1..2}/file{1..3}
mkdir: created directory ‘test’
mkdir: created directory ‘test/directory’
mkdir: created directory ‘test/directory/subdirectory1’
mkdir: created directory ‘test/directory/subdirectory1/file1’
mkdir: created directory ‘test/directory/subdirectory1/file2’
mkdir: created directory ‘test/directory/subdirectory1/file3’
mkdir: created directory ‘test/directory/subdirectory2’
mkdir: created directory ‘test/directory/subdirectory2/file1’
mkdir: created directory ‘test/directory/subdirectory2/file2’
mkdir: created directory ‘test/directory/subdirectory2/file3’
21
répondu Ryan 2012-05-30 14:10:21

les options getopts ne peuvent prendre que zéro ou un argument. Vous pourriez vouloir changer votre interface pour supprimer l'option-f, et juste itérer sur les arguments restants non-option

usage: foo.sh -i end -d dir -s subdir file [...]

Donc,

while getopts ":i:d:s:" opt; do
  case "$opt" in
    i) initial=$OPTARG ;;
    d) dir=$OPTARG ;;
    s) sub=$OPTARG ;;
  esac
done
shift $(( OPTIND - 1 ))

path="/$initial/$dir/$sub"
mkdir -p "$path"

for file in "$@"; do
  touch "$path/$file"
done
18
répondu glenn jackman 2011-09-23 14:10:22

j'ai résolu le même problème que vous aviez comme ceci

au lieu de

foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3

faire ceci:

foo.sh -i test -d directory -s "subdirectory subdirectory2" -f "file1 file2 file3"

avec le séparateur d'espace, vous pouvez simplement l'exécuter avec une boucle de base voici le code

while getopts ":i:d:s:f:" opt
   do
     case $opt in
        i ) initial=$OPTARG;;
        d ) dir=$OPTARG;;
        s ) sub=$OPTARG;;
        f ) files=$OPTARG;;

     esac
done

for subdir in $sub;do
   for file in $files;do
      echo $subdir/$file
   done   
done

Voici un samle de sortie:

$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
7
répondu David Berkan 2011-12-28 15:54:09

il y a en fait un moyen de récupérer plusieurs arguments en utilisant getopts, mais il faut du piratage manuel avec getopts' OPTIND variable.

voir le script suivant (reproduit ci-dessous):https://gist.github.com/achalddave/290f7fcad89a0d7c3719. Il y a probablement un moyen plus facile, mais c'était le moyen le plus rapide que j'ai pu trouver.

#!/bin/sh

usage() {
cat << EOF
 -a <a1> <a2> <a3> [-b] <b1> [-c]
    -a      First flag; takes in 3 arguments
    -b      Second flag; takes in 1 argument
    -c      Third flag; takes in no arguments
EOF
}

is_flag() {
    # Check if  is a flag; e.g. "-b"
    [[ "" =~ -.* ]] && return 0 || return 1
}

# Note:
# For a, we fool getopts into thinking a doesn't take in an argument
# For b, we can just use getopts normal behavior to take in an argument
while getopts "ab:c" opt ; do
    case "${opt}" in
        a)
            # This is the tricky part.

            # $OPTIND has the index of the _next_ parameter; so "$$((OPTIND))"
            # will give us, e.g., . Use eval to get the value in .

            eval "a1=$$((OPTIND))"
            eval "a2=$$((OPTIND+1))"
            eval "a3=$$((OPTIND+2))"

            # Note: We need to check that we're still in bounds, and that
            # a1,a2,a3 aren't flags. e.g.
            #   ./getopts-multiple.sh -a 1 2 -b
            # should error, and not set a3 to be -b.
            if [[ $((OPTIND+2)) > $# ]] || is_flag "$a1" || is_flag "$a2" || is_flag "$a3"
            then
                usage
                echo
                echo "-a requires 3 arguments!"
                exit
            fi

            echo "-a has arguments $a1, $a2, $a3"

            # "shift" getopts' index
            OPTIND=$((OPTIND+3))
            ;;
        b)
            # Can get the argument from getopts directly
            echo "-b has argument $OPTARG"
            ;;
        c)
            # No arguments, life goes on
            echo "-c"
            ;;
    esac
done
4
répondu Achal Dave 2014-03-27 18:24:57

la question originale traite de getopts, mais il y a une autre solution qui offre des fonctionnalités plus flexibles sans getopts (c'est peut-être un peu plus verbeux, mais fournit une interface en ligne de commande beaucoup plus flexible). Voici un exemple:

while [[ $# > 0 ]]
do
    key=""
    case $key in
        -f|--foo)
            nextArg=""
            while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
                case $nextArg in
                    bar)
                        echo "--foo bar found!"
                    ;;
                    baz)
                        echo "--foo baz found!"
                    ;;
                    *)
                        echo "$key $nextArg found!"
                    ;;
                esac
                if ! [[ "" =~ -.* ]]; then
                    shift
                    nextArg=""
                else
                    shift
                    break
                fi
            done
        ;;
        -b|--bar)
            nextArg=""
            while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
                case $nextArg in
                    foo)
                        echo "--bar foo found!"
                    ;;
                    baz)
                        echo "--bar baz found!"
                    ;;
                    *)
                        echo "$key $nextArg found!"
                    ;;
                esac
                if ! [[ "" =~ -.* ]]; then
                    shift
                    nextArg=""
                else
                    shift
                    break
                fi
            done
        ;;
        -z|--baz)
            nextArg=""
            while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do

                echo "Doing some random task with $key $nextArg"

                if ! [[ "" =~ -.* ]]; then
                    shift
                    nextArg=""
                else
                    shift
                    break
                fi
            done
        ;;
        *)
            echo "Unknown flag $key"
        ;;
    esac
    shift
done

dans cet exemple, nous faisons une boucle à travers toutes les options de ligne de commande à la recherche de paramètres qui correspondent à nos options de ligne de commande acceptées (comme-F ou --foo). Une fois que nous trouvons un drapeau, on boucle sur chaque paramètre jusqu'à ce que nous soyons à court de paramètres ou que nous rencontrions un autre drapeau. Cela nous fait revenir dans notre boucle extérieure qui ne traite que les drapeaux.

Avec cette configuration, les commandes suivantes sont équivalentes:

script -f foo bar baz
script -f foo -f bar -f baz
script --foo foo -f bar baz
script --foo foo bar -f baz

vous pouvez aussi analyser des ensembles de paramètres incroyablement désorganisés tels que:

script -f baz derp --baz herp -z derp -b foo --foo bar -q llama --bar fight

pour obtenir la sortie:

--foo baz found!
-f derp found!
Doing some random task with --baz herp
Doing some random task with -z derp
--bar foo found!
--foo bar found!
Unknown flag -q
Unknown flag llama
--bar fight found!
3
répondu Brendon Dugan 2016-09-28 18:21:17

si vous voulez spécifier n'importe quel nombre de valeurs pour une option, vous pouvez utiliser une boucle simple pour les trouver et les empiler dans un tableau. Par exemple, modifions l'exemple de L'OP pour permettre n'importe quel nombre de paramètres-s:

unset -v sub
while getopts ":i:d:s:f:" opt
   do
     case $opt in
        i ) initial=$OPTARG;;
        d ) dir=$OPTARG;;
        s ) sub=("$OPTARG")
            until [[ $(eval "echo ${$OPTIND}") =~ ^-.* ]] || [ -z $(eval "echo ${$OPTIND}") ]; do
                sub+=($(eval "echo ${$OPTIND}"))
                OPTIND=$((OPTIND + 1))
            done
            ;;
        f ) files=$OPTARG;;
     esac
done

ceci prend le premier argument ($OPTARG) et le met dans le tableau $sub. Ensuite, il va continuer à chercher à travers les paramètres restants jusqu'à ce qu'il atteigne un autre paramètre pointillé ou il n'y a plus d'arguments à évaluer. S'il en trouve plus les paramètres qui ne sont pas des paramètres pointillés, il les ajoute au tableau $sub et booste la variable $OPTIND.

ainsi, dans L'exemple de L'OP, ce qui suit pourrait être exécuté:

foo.sh -i test -d directory -s subdirectory1 subdirectory2 -f file1

si nous avons ajouté ces lignes au script pour démontrer:

echo ${sub[@]}
echo ${sub[1]}
echo $files

Le résultat:

subdirectory1 subdirectory2
subdirectory2
file1
2
répondu KoZm0kNoT 2017-03-17 12:40:57

comme vous ne montrez pas comment vous espérez construire votre liste

/test/directory/subdirectory/file1
. . .
test/directory/subdirectory2/file3

il est un peu difficile de savoir comment procéder, mais fondamentalement, vous devez continuer à ajouter de nouvelles valeurs à la variable appropriée, i.e.

 case $opt in
    d ) dirList="${dirList} $OPTARG" ;;
 esac

notez que sur la première passe dir sera vide, et vous finirez avec un espace menant à la de de votre valeur finale pour ${dirList}. (Si vous avez vraiment besoin de code qui n'inclut pas d'espaces supplémentaires, avant ou arrière, il y a une commande que je peux vous montrer, mais il va être difficile à comprendre, et il ne semble pas que vous en aurez besoin ici, mais laissez-moi savoir)

vous pouvez alors envelopper vos variables de liste dans des boucles pour émettre toutes les valeurs, i.e.

for dir in ${dirList} do
   for f in ${fileList} ; do
      echo $dir/$f
   done
done

enfin, il est considéré comme une bonne pratique de "piéger" toute entrée inconnue dans votre énoncé de cas, c.-à-d.

 case $opt in
    i ) initial=$OPTARG;;
    d ) dir=$OPTARG;;
    s ) sub=$OPTARG;;
    f ) files=$OPTARG;;
    * ) 
       printf "unknown flag supplied "${OPTARG}\nUsageMessageGoesHere\n"
       exit 1
    ;;

 esac 

j'espère que cette aide.

0
répondu shellter 2011-09-23 14:05:12