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?
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
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’
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
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
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
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!
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
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.