bash - programar - shell script linux español
Recuperando múltiples argumentos para una sola opción usando getopts en Bash (8)
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
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?
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 declaración 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.
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 <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.
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 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!
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
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’
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