Recuperando múltiples argumentos para una sola opción usando getopts en Bash

Necesito ayuda con getopts .

Creé un script Bash que se ve así cuando se ejecuta:

$ foo.sh -i env -d directorio -s subdirectorio -f archivo

Funciona correctamente cuando se maneja un argumento de cada indicador. Pero cuando invoco varios argumentos de cada indicador, no estoy seguro de cómo extraer la información variable múltiple de las variables en 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 

Después de tomar las opciones, quiero construir estructuras de directorio a partir de las variables

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

Entonces la estructura del directorio sería

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

¿Algunas ideas?

Puede usar la misma opción varias veces y agregar todos los valores a una matriz .

Para la pregunta original muy específica aquí, la solución mkdir -p Ryan es obviamente la mejor.

Sin embargo, para la cuestión más general de obtener múltiples valores de la misma opción con getopts , aquí está:

 #!/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 salida sería:

 $ /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 

Sé que esta pregunta es antigua, pero quería arrojar esta respuesta aquí en caso de que alguien venga en busca de una respuesta.

Los shells como BASH soportan la creación recursiva de directorios de esta manera, por lo que un script no es realmente necesario. Por ejemplo, el póster original quería algo como:

 $ 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 

Esto se hace fácilmente con esta línea de comando:

 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' 

O incluso un poco más corto:

 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' 

O más corto, con más conformidad:

 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' 

O por último, usando secuencias:

 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' 

Las opciones getopts solo pueden tomar cero o un argumento. Es posible que desee cambiar su interfaz para eliminar la opción -f, y simplemente iterar sobre los argumentos restantes que no son de opción

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

Asi que,

 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 

Arreglé el mismo problema que tenías así

en lugar de

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

hacer esto:

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

con el separador de espacio puede simplemente ejecutarlo con un bucle básico aquí está el código

 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 

Aquí hay una salida de samle:

 $ ./getopts.sh -s "testdir1 testdir2" -f "file1 file2 file3" testdir1/file1 testdir1/file2 testdir1/file3 testdir2/file1 testdir2/file2 testdir2/file3 

De hecho, hay una manera de recuperar múltiples argumentos utilizando getopts , pero requiere algunos hackeo manuales con la variable getopts de OPTIND .

Vea la siguiente secuencia de comandos (se reproduce a continuación): https://gist.github.com/achalddave/290f7fcad89a0d7c3719 . Probablemente haya una manera más fácil, pero esta fue la forma más rápida que pude encontrar.

 #!/bin/sh usage() { cat << EOF $0 -a    [-b]  [-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; eg "-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, eg, $2. Use eval to get the value in $2. 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. eg # ./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 pregunta original trata con getopts, pero hay otra solución que proporciona una funcionalidad más flexible sin getopts (esto es quizás un poco más detallado, pero proporciona una interfaz de línea de comando mucho más flexible). Aquí hay un ejemplo:

 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 

En este ejemplo, estamos recorriendo todas las opciones de línea de comando buscando parámetros que coincidan con nuestros indicadores de línea de comando aceptados (como -f o –foo). Una vez que encontramos una bandera, recorremos todos los parámetros hasta que nos quedemos sin parámetros o encontramos otra bandera. Esto nos devuelve a nuestro bucle externo que solo procesa banderas.

Con esta configuración, los siguientes comandos son equivalentes:

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

También puede analizar conjuntos de parámetros increíblemente desorganizados tales como:

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

Para obtener el resultado:

 --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 desea especificar cualquier cantidad de valores para una opción, puede usar un bucle simple para encontrarlos y rellenarlos en una matriz. Por ejemplo, modifiquemos el ejemplo del OP para permitir cualquier cantidad de parámetros -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 

Esto toma el primer argumento ($ OPTARG) y lo coloca en la matriz $ sub. Luego continuará buscando a través de los parámetros restantes hasta que llegue a otro parámetro discontinuo O ya no haya más argumentos para evaluar. Si encuentra más parámetros que no son un parámetro discontinuo, lo agrega a la matriz $ sub y aumenta la variable $ OPTIND.

Entonces, en el ejemplo de OP, podría ejecutarse lo siguiente:

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

Si agregamos estas líneas a la secuencia de comandos para demostrar:

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

La salida sería:

 subdirectory1 subdirectory2 subdirectory2 file1 

Como no muestra cómo espera construir su lista

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

no está claro cómo proceder, pero básicamente necesita agregar nuevos valores a la variable apropiada, es decir,

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

Tenga en cuenta que en el primer paso, el directorio estará vacío, y terminará con un espacio que lleva al valor final de ${dirList} . (Si realmente necesita un código que no incluya espacios adicionales, frontal o posterior, hay un comando que puedo mostrarle, pero será difícil de entender, y no parece que lo necesite aquí, pero avísame)

A continuación, puede ajustar sus variables de lista para bucles para emitir todos los valores, es decir,

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

Finalmente, se considera una buena práctica ‘atrapar’ cualquier entrada desconocida en su statement de caso, es decir,

  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 

Espero que esto ayude.

    Intereting Posts