J'ai besoin d'aide avec getopts
.
J'ai créé un script Bash qui ressemble à ceci à l'exécution:
$ foo.sh -i env -d répertoire -s sous-répertoire fichier -f
Cela fonctionne correctement lorsque vous manipulez un argument de chaque drapeau. Mais lorsque j'appelle plusieurs arguments de chaque drapeau, je ne suis pas sûr de savoir comment extraire les informations de variable multiple des variables 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 souhaite ensuite construire des structures de répertoires à partir des variables
foo.sh -i test -d directory -s subdirectory -s subdirectory2 -f file1 file2 file3
Ensuite, la structure de 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?
Vous pouvez utiliser plusieurs fois la même option et ajouter toutes les valeurs à un tableau .
Pour la question originale très spécifique ici, la solution mkdir -p
de Ryan est évidemment la meilleure.
Cependant, pour la question plus générale de obtenir plusieurs valeurs de la même option avec getopts , la voici:
#!/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
La sortie serait:
$ /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 jeter cette réponse ici au cas où quelqu'un viendrait chercher une réponse.
Des shells comme BASH prennent déjà en charge la création de répertoires de manière récursive, un script n’est donc 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 court, avec plus de 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 de getopts ne peuvent prendre que zéro ou un argument. Vous voudrez peut-être modifier votre interface pour supprimer l'option -f, et simplement parcourir les arguments non-option restants
usage: foo.sh -i end -d dir -s subdir file [...]
Alors,
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 corrigé 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 le parcourir avec une boucle de base .
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 exemple de sortie:
$ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3"
testdir1/file1
testdir1/file2
testdir1/file3
testdir2/file1
testdir2/file2
testdir2/file3
Il existe en fait un moyen de récupérer plusieurs arguments en utilisant getopts
, mais cela nécessite un piratage manuel avec la variable getopts
'OPTIND
.
Voir le script suivant (reproduit ci-dessous): https://Gist.github.com/achalddave/290f7fcad89a0d7c3719 . Il y a probablement un moyen plus facile, mais c'est le moyen le plus rapide que j'ai pu trouver.
#!/bin/sh
usage() {
cat << EOF
$0 -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 $1 is a flag; e.g. "-b"
[[ "$1" =~ -.* ]] && 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., ${2}. Use eval to get the value in ${2}.
# The {} are needed in general for the possible case of multiple digits.
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)) -gt $# ] || 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
Si vous souhaitez spécifier un nombre quelconque de valeurs pour une option, vous pouvez utiliser une simple boucle pour les rechercher et les insérer dans un tableau. Par exemple, modifions l'exemple de l'OP pour autoriser un nombre quelconque 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 place dans le tableau $ sub. Ensuite, il continuera à chercher parmi les paramètres restants jusqu'à ce qu'il rencontre un autre paramètre en pointillé OR. Il n'y a plus d'arguments à évaluer. S'il trouve plus de paramètres qui ne sont pas des paramètres en pointillés, il les ajoute au tableau $ sub et amplifie la variable $ OPTIND.
Ainsi, dans l'exemple du PO, on pourrait exécuter:
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
La sortie serait:
subdirectory1 subdirectory2
subdirectory2
file1
La question initiale traitait de getopts, mais il existe une autre solution offrant des fonctionnalités plus souples sans getopts (c'est peut-être un peu plus détaillé, mais fournit une interface de ligne de commande beaucoup plus flexible). Voici un exemple:
while [[ $# > 0 ]]
do
key="$1"
case $key in
-f|--foo)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
bar)
echo "--foo bar found!"
;;
baz)
echo "--foo baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-b|--bar)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
case $nextArg in
foo)
echo "--bar foo found!"
;;
baz)
echo "--bar baz found!"
;;
*)
echo "$key $nextArg found!"
;;
esac
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
-z|--baz)
nextArg="$2"
while ! [[ "$nextArg" =~ -.* ]] && [[ $# > 1 ]]; do
echo "Doing some random task with $key $nextArg"
if ! [[ "$2" =~ -.* ]]; then
shift
nextArg="$2"
else
shift
break
fi
done
;;
*)
echo "Unknown flag $key"
;;
esac
shift
done
Dans cet exemple, nous parcourons toutes les options de la ligne de commande en recherchant les paramètres qui correspondent à nos indicateurs de ligne de commande acceptés (tels que -f ou --foo). Une fois que nous avons trouvé un indicateur, nous parcourons chaque paramètre jusqu'à ce que nous manquions de paramètres ou que nous rencontrions un autre indicateur. Cela nous ramène dans notre boucle externe 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 également 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 le résultat:
--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!
#!/bin/bash
myname=$(basename "$0")
# help function
help () { cat <<EOP
$myname: -c cluster [...] -a action [...] -i instance [...]
EOP
}
# parse sub options
get_opts () {
rs='' && rc=0 # return string and return code
while [[ $# -gt 0 ]]; do
shift
[[ "$1" =~ -.* ]] && break || rs="$rs $1" && rc=$((rc + 1))
done
echo "$rs"
}
#parse entire command-line
while [[ $# -gt 0 ]]; do
case $1 in
"-a") ACTS="$(get_opts $@)"
;;
"-i") INSTS=$(get_opts $@)
;;
"-c") CLUSTERS=$(get_opts $@)
;;
"-h") help
;;
?) echo "sorry, I dont do $1"
exit
;;
esac
shift
done
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 vous devez fondamentalement continuer à ajouter de nouvelles valeurs à la variable appropriée, c.-à-d.
case $opt in
d ) dirList="${dirList} $OPTARG" ;;
esac
Notez que lors du premier passage, le répertoire sera vide et vous obtiendrez un espace devant le début de votre valeur finale pour ${dirList}
. (Si vous avez vraiment besoin de code qui n'inclut pas d'espaces supplémentaires, recto ou verso, il y a une commande que je peux vous montrer, mais ce sera difficile à comprendre et il ne semble pas que vous en ayez besoin ici, mais faites le moi savoir)
Vous pouvez ensuite envelopper les variables de votre liste dans les boucles for pour émettre toutes les valeurs, c.-à-d.
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 déclaration 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 ça aide.