En un script Bash, estoy tratando de almacenar las opciones que estoy usando rsync
en una variable separada. Esto funciona bien para opciones simples (como --recursive
), pero me encuentro con problemas con --exclude='.*'
:
$ find source
source
source/.bar
source/foo
$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo
sent 57 bytes received 19 bytes 152.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
$ RSYNC_OPTIONS="-rnv --exclude='.*'"
$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo
sent 78 bytes received 22 bytes 200.00 bytes/sec
total size is 0 speedup is 0.00 (DRY RUN)
Como puede ver, pasar --exclude='.*'
a rsync
"manualmente" funciona bien ( .bar
no se copia), no funciona cuando las opciones se almacenan primero en una variable.
Supongo que esto está relacionado con las comillas o el comodín (o ambos), pero no he podido averiguar qué es exactamente lo que está mal.
Respuestas:
En general, es una mala idea degradar una lista de elementos separados en una sola cadena, sin importar si es una lista de opciones de línea de comando o una lista de nombres de ruta.
Usando una matriz en su lugar:
o
y después...
De esta forma, se mantiene la cotización de las opciones individuales (siempre y cuando doble la expansión de
${rsync_options[@]}
). También le permite manipular fácilmente las entradas individuales de la matriz, en caso de que necesite hacerlo, antes de llamarrsync
.En cualquier shell POSIX, uno puede usar la lista de parámetros posicionales para esto:
Nuevamente, la doble cita de la expansión de
$@
es crítica aquí.Relacionado tangencialmente:
El problema es que cuando coloca los dos conjuntos de opciones en una cadena, las comillas simples
--exclude
del valor de la opción se convierten en parte de ese valor. Por lo tanto,habría funcionado¹ ... pero es mejor (como más seguro) usar una matriz o los parámetros posicionales con entradas citadas individualmente. Hacerlo también le permitiría usar cosas con espacios en ellas, si fuera necesario, y evitaría que el shell realice la generación de nombre de archivo (globalización) en las opciones.
¹ siempre que
$IFS
no se modifique y que no hay ningún archivo cuyo nombre empieza con--exclude=.
en el directorio actual, y que losnullglob
ofailglob
no está ajustada concha opciones.fuente
@Kusalananda ya ha explicado el problema básico y cómo resolverlo, y la entrada de preguntas frecuentes de Bash vinculada por @glenn jackmann también proporciona mucha información útil. Aquí hay una explicación detallada de lo que está sucediendo en mi problema basado en estos recursos.
Usaremos un pequeño script que imprima cada uno de sus argumentos en una línea separada para ilustrar cosas (
argtest.bash
):Opciones de paso "manualmente":
Como se esperaba, las partes
-rnv
y--exclude='.*'
se dividen en dos argumentos, ya que están separados por espacios en blanco sin comillas (esto se llama división de palabras ).También tenga en cuenta que las comillas
.*
se han eliminado: las comillas simples le dicen al shell que pase su contenido sin una interpretación especial , pero las comillas mismas no se pasan al comando .Si ahora almacenamos las opciones en una variable como una cadena (en lugar de usar una matriz), las comillas no se eliminan :
Esto se debe a dos razones: las comillas dobles utilizadas al definir
$OPTS
evitan el tratamiento especial de las comillas simples, por lo que estas últimas son parte del valor:Cuando ahora lo usamos
$OPTS
como argumento para un comando, las comillas se procesan antes de la expansión del parámetro , por lo que las comillas$OPTS
ocurren "demasiado tarde".Esto significa que (en mi problema original)
rsync
usa el patrón de exclusión'.*'
(¡con comillas!) En lugar del patrón.*
; excluye los archivos cuyo nombre comienza con una comilla simple seguido de un punto y termina con una comilla simple. Obviamente, eso no es lo que se pretendía.Una solución habría sido omitir las comillas dobles al definir
$OPTS
:Sin embargo, es una buena práctica citar siempre las asignaciones de variables debido a diferencias sutiles en casos más complejos.
Como señaló @Kusalananda, no citar
.*
también habría funcionado. Había agregado las citas para evitar la expansión del patrón , pero eso no era estrictamente necesario en este caso especial :Resulta que Bash hace realizar expansión de los patrones, pero el patrón
--exclude=.*
no coincide con ningún archivo, por lo que el patrón se pasa al comando. Comparar:Sin embargo, no citar el patrón es peligroso, porque si (por alguna razón) hubo una coincidencia de archivos
--exclude=.*
, el patrón se expande:Finalmente, veamos por qué usar una matriz evita mi problema de citas (además de las otras ventajas de usar matrices para almacenar argumentos de comando).
Al definir la matriz, la división de palabras y el manejo de comillas ocurren como se esperaba:
Al pasar las opciones al comando, usamos la sintaxis
"${ARRAY[@]}"
, que expande cada elemento de la matriz en una palabra separada:fuente
Cuando escribimos funciones y scripts de shell, en los cuales los argumentos se pasan para ser procesados, los argumentos se pasarán en variables con nombres numéricos, por ejemplo, $ 1, $ 2, $ 3
Por ejemplo :
bash my_script.sh Hello 42 World
En el interior
my_script.sh
, los comandos se usarán$1
para referirse a Hello,$2
to42
y$3
forWorld
La referencia de variable
$0
, se expandirá al nombre del script actual, p. Ej.my_script.sh
No juegues todo el código con comandos como variables.
Tener en cuenta :
1 Evite usar nombres de variables con mayúsculas en los scripts.
2 No use comillas inversas, use $ (...) en su lugar, anida mejor.
fuente