Leí sobre eso debería citar variables en bash, por ejemplo, "$ foo" en lugar de $ foo. Sin embargo, mientras escribía un script, encontré un caso en el que funciona sin comillas pero no con ellas:
wget_options='--mirror --no-host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line
relative_path="$3" # /XXX received from command line
Este funciona:
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"
Este no (tenga en cuenta las comillas dobles alrededor de $ wget_options):
wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
¿Cuál es la razón para esto?
Es la primera línea la buena versión; ¿O debería sospechar que hay un error oculto en algún lugar que causa este comportamiento?
En general, ¿dónde encuentro buena documentación para entender cómo funciona bash y sus citas? Al escribir este script, siento que comencé a trabajar en una base de prueba y error en lugar de entender las reglas.
wget
no sabe qué--mirror --no-host-directories
significa (como un argumento), pero lo maneja cuando se divide en dos argumentos. Muy pocos programas tratan espacios y comillas especialmente una vez que están dentro del vector de argumento. El problema es quebash
, y otros shells, deben ser>bash
, por lo que puede imaginar que$a
es exactamente equivalente a escribir directamente su contenido. Ahora el problema es evidente: sea="-a -b"; cmd "$a"
expandecmd "-a -b"
, perocmd
probablemente no sabe lo que eso significa.cmd $a
expande acmd -a -b
, lo que probablemente hace el trabajo.Respuestas:
Básicamente, debe duplicar las expansiones de variables entre comillas para protegerlas de la división de palabras (y la generación de nombre de archivo). Sin embargo, en tu ejemplo,
la división de palabras es exactamente lo que quieres .
Con
"$wget_options"
(citado),wget
no sabe qué hacer con el argumento único--mirror --no-host-directories
y se quejaPara
wget
ver las dos opciones--mirror
y,--no-host-directories
por separado, tiene que ocurrir la división de palabras.Hay formas más robustas de hacer esto. Si está utilizando
bash
o cualquier otro shell que utiliza matrices comobash
do, consulte la respuesta de Glenn Jackman . La respuesta de Gilles describe adicionalmente una solución alternativa para conchas más simples como el estándar/bin/sh
. Ambos esencialmente almacenan cada opción como un elemento separado en una matriz.Pregunta relacionada con buenas respuestas: ¿Por qué mi script de shell se ahoga en espacios en blanco u otros caracteres especiales?
La doble cita de expansiones variables es una buena regla general. Hacer eso . Entonces tenga en cuenta los muy pocos casos en los que no debe hacer eso. Estos se le presentarán a través de mensajes de diagnóstico, como el mensaje de error anterior.
También hay algunos casos en los que no necesita citar expansiones variables. Pero de todos modos es más fácil continuar usando comillas dobles, ya que no hace mucha diferencia. Uno de esos casos es
Otro es
fuente
$IFS
contiene el valor correcto. Aquí debe dividirse en el espacio y el texto no contiene ninguna pestaña o nueva línea, por lo que el valor predeterminado de$IFS
sería suficiente, pero si ese código se va a usar en una función que se puede invocar en un contexto donde$IFS
podría haberse modificado , querrás configurarlo de$IFS
antemano (y posiblemente restaurarlo después o usar un ámbito local si el resto del código asume que no se ha modificado$IFS
)La forma más sólida de codificar es usar una matriz:
fuente
cp
yrsync
harán cosas inesperadas si su comando se expande a algo asírsync '' rest of parameters
. Esto es ideal para construir un comando pieza por pieza condicionalmente y luego ejecutarlo una vez en un solo lugar.Está intentando almacenar una lista de cadenas en una variable de cadena. No encaja No importa cómo acceda a la variable, algo está roto.
wget_options='--mirror --no-host-directories'
establece la variablewget_options
en una cadena que contiene un espacio. En este punto, no hay forma de saber si se supone que el espacio es parte de una opción o un separador entre las opciones.Cuando accede a la variable con una sustitución entre comillas
wget "$wget_options"
, el valor de la variable se usa como una cadena. Esto significa que se pasa como un parámetro único awget
, por lo que es una sola opción. Esto se rompe en su caso porque pretendía que significara múltiples opciones.Cuando utiliza una sustitución sin comillas
wget $wget_options
, el valor de la variable de cadena se somete a un proceso de expansión denominado "split + glob":$IFS
variable). Esto da como resultado una lista intermedia de cadenas.Esto sucede en su ejemplo, porque el proceso de división convierte el espacio en un separador, pero no funciona en general, ya que una opción podría contener espacios y caracteres comodín.
En ksh, bash, yash y zsh, puede usar una variable de matriz. Una matriz en terminología de shell es una lista de cadenas, por lo que no hay pérdida de información. Para hacer una variable de matriz, ponga paréntesis alrededor de los elementos de la matriz al asignar el valor a la variable. Para acceder a todos los elementos de la matriz, use : esta es una generalización de , que forma una lista de los elementos de la matriz. Tenga en cuenta que aquí también necesita las comillas dobles, de lo contrario, cada elemento se somete a split + glob.
"${VARIABLE[@]}"
"$@"
En simple sh, no hay variables de matriz. Si no le importa perder los argumentos posicionales, puede usarlos para almacenar una lista de cadenas.
Para obtener más información, consulte ¿Por qué mi script de shell se ahoga en espacios en blanco u otros caracteres especiales?
fuente
(set -- ...; exec wget "$@" ...)
.