La regla general en las secuencias de comandos de shell es que las variables siempre se deben citar a menos que haya una razón convincente para no hacerlo. Para obtener más detalles de los que probablemente quiera saber, eche un vistazo a estas excelentes preguntas y respuestas: implicaciones de seguridad de olvidarse de citar una variable en shells bash / POSIX .
Considere, sin embargo, una función como la siguiente:
run_this(){
$@
}
¿Debería $@
ser citado allí o no? Jugué un poco con él y no pude encontrar ningún caso en el que la falta de comillas causara un problema. Por otro lado, el uso de las comillas hace que se rompa al pasar un comando que contiene espacios como una variable entre comillas:
#!/usr/bin/sh
set -x
run_this(){
$@
}
run_that(){
"$@"
}
comm="ls -l"
run_this "$comm"
run_that "$comm"
Ejecutar el script anterior devuelve:
$ a.sh
+ comm='ls -l'
+ run_this 'ls -l'
+ ls -l
total 8
-rw-r--r-- 1 terdon users 0 Dec 22 12:58 da
-rw-r--r-- 1 terdon users 45 Dec 22 13:33 file
-rw-r--r-- 1 terdon users 43 Dec 22 12:38 file~
+ run_that 'ls -l'
+ 'ls -l'
/home/terdon/scripts/a.sh: line 7: ls -l: command not found
Puedo evitar eso si lo uso en run_that $comm
lugar de run_that "$comm"
, pero como la función run_this
(sin comillas) funciona con ambos, parece ser la apuesta más segura.
Entonces, en el caso específico de usar $@
en una función cuyo trabajo es ejecutar $@
como un comando, ¿debería $@
citarse? Explique por qué debe / no debe citarse y proporcione un ejemplo de datos que puedan romperlo.
fuente
run_that
El comportamiento es definitivamente lo que esperaría (¿y si hay un espacio en el camino hacia el comando?). Si quisiera el otro comportamiento, ¿seguramente lo citaría en el sitio de la llamada donde sabe cuáles son los datos? Esperaría llamar a esta función comorun_that ls -l
, que funciona igual en cualquier versión. ¿Hay un caso que te hizo esperar de manera diferente?${mycmd[@]}
.Respuestas:
El problema radica en cómo se pasa el comando a la función:
"$@"
debe usarse en el caso general donde surun_this
función está prefijada a un comando normalmente escrito.run_this
lleva a citar el infierno:No estoy seguro de cómo debo pasar un nombre de archivo con espacios
run_this
.fuente
run_this
.$@
citar accidentalmente. Debería haber dejado un ejemplo. : Drun_this
. Este es básicamente el mismo problema con el que se encuentra al insertar comandos complejos en cadenas como se describe en la Pregunta frecuente 050 de Bash .Es cualquiera:
O:
o:
Pero:
No tiene mucho sentido
Si desea ejecutar el
ls -l
comando (no ells
comando conls
y-l
como argumentos), haría:Pero si (más probable), es el
ls
comando conls
y-l
como argumentos, ejecutarías:Ahora, si es más que un simple comando que desea ejecutar, si desea hacer asignaciones variables, redirecciones, tuberías ..., solo
interpret_this_shell_code
hará:aunque por supuesto siempre puedes hacer:
fuente
Mirándolo desde la perspectiva bash / ksh / zsh,
$*
y$@
son un caso especial de expansión de matriz general. Las expansiones de matriz no son como las expansiones variables normales:Con las
$*
/${a[*]}
expansiones obtienes la matriz unida con el primer valor deIFS
—que es el espacio por defecto— en una cadena gigante. Si no lo cita, se divide como lo haría una cadena normal.Con las
$@
/${a[@]}
expansiones, el comportamiento depende de si la$@
/${a[@]}
expansión se cita o no:"$@"
o"${a[@]}"
), obtienes el equivalente de"$1" "$2" "$3" #...
o"${a[1]}" "${a[2]}" "${a[3]}" # ...
$@
o${a[@]}
) obtienes el equivalente de$1 $2 $3 #...
o${a[1]} ${a[2]} ${a[3]} # ...
Para envolver comandos, definitivamente desea las @ expansiones (1.) entre comillas .
Más buena información sobre las matrices bash (y bash-like): https://lukeshu.com/blog/bash-arrays.html
fuente
Como cuando no cita dos veces
$@
, dejó todos los problemas globales en el enlace que le dio a su función.¿Cómo podrías ejecutar un comando llamado
*
? No puedes hacerlo conrun_this
:Y ves, incluso cuando ocurrió un error,
run_that
te dio un mensaje más significativo.La única forma de expandirse
$@
a palabras individuales es comillas dobles. Si desea ejecutarlo como un comando, debe pasar el comando y sus parámetros como palabras separadas. Que lo que hizo en el lado de la persona que llama, no dentro de su función.Es una mejor opción. O si su shell admite matrices:
Incluso cuando el shell no es compatible con la matriz, puedes jugar con él usando
"$@"
.fuente
La ejecución de variables en
bash
es una técnica propensa a fallas. Es simplemente imposible escribir unarun_this
función que maneje correctamente todos los casos límite, como:ls | grep filename
)ls > /dev/null
. ej. )if
while
etc.Si todo lo que quiere hacer es evitar la repetición del código, es mejor que use las funciones. Por ejemplo, en lugar de:
Deberías escribir
Si los comandos solo están disponibles en tiempo de ejecución, debe usarlo
eval
, que está específicamente diseñado para manejar todas las peculiaridades que harán querun_this
falle:Tenga en cuenta que
eval
es conocido por problemas de seguridad, pero si pasa variables de fuentes no confiables arun_this
, también enfrentará la ejecución de código arbitrario.fuente
La decisión es tuya. Si no cita
$@
ninguno de sus valores, se someterá a una expansión e interpretación adicionales. Si lo cita, todos los argumentos pasados la función se reproducen literalmente en su expansión. Nunca podrá manejar de manera confiable tokens de sintaxis de shell como,&>|
etc. de cualquier manera sin analizar los argumentos usted mismo de todos modos, por lo que le quedan las opciones más razonables de administrar su función:"$@"
....o...
$@
.De ninguna manera está mal si es intencional y si los efectos de lo que eliges se comprenden bien. Ambas formas tienen ventajas una sobre la otra, aunque las ventajas de la segunda rara vez son particularmente útiles. Todavía...
... no es inútil , rara vez es muy útil . Y en un
bash
shell, porquebash
por defecto no adhiere una definición variable a su entorno, incluso cuando dicha definición se antepone a la línea de comando de una función incorporada especial o a una función, el valor global de$IFS
no se ve afectado y su declaración es local solo a larun_this()
llamada.Similar:
... el globbing también es configurable. Las citas tienen un propósito: no son para nada. Sin ellos, la expansión de shell experimenta una interpretación adicional: interpretación configurable . Solía ser, con algunos shells muy antiguos , que
$IFS
se aplicaba globalmente a todas las entradas, y no solo a las expansiones. De hecho, dichos shells se comportaron de la misma manerarun_this()
que rompieron todas las palabras de entrada sobre el valor de$IFS
. Entonces, si lo que estás buscando es ese comportamiento de shell muy antiguo, entonces deberías usarlorun_this()
.No lo estoy buscando, y en este momento me cuesta bastante encontrar un ejemplo útil para ello. Generalmente prefiero que los comandos que ejecuta mi shell sean los que escribo en él. Y así, dada la opción, casi siempre lo haría
run_that()
. Excepto eso...Se puede citar casi cualquier cosa. Los comandos se ejecutarán entre comillas. Funciona porque para cuando el comando se ejecuta realmente, todas las palabras de entrada ya se han eliminado entre comillas , que es la última etapa del proceso de interpretación de entrada del shell. Entonces, la diferencia entre
'ls'
yls
solo puede importar mientras el shell está interpretando, y es por eso que las citasls
aseguran que cualquier alias nombradols
no sea sustituido por mils
palabra de comando citada . Aparte de eso, las únicas cosas que afectan las citas son la delimitación de palabras (que es cómo y por qué funciona la cita variable / input-whitespace) , y la interpretación de metacaracteres y palabras reservadas.Entonces:
Nunca vas a ser capaz de hacer eso con cualquiera de
run_this()
orun_that()
.Pero los nombres de funciones, o
$PATH
los comandos 'd', o los builtins se ejecutarán entre comillas o sin comillas, y eso es exactamente cómorun_this()
y cómorun_that()
funcionan en primer lugar. No podrás hacer nada útil con$<>|&(){}
ninguno de esos. Corto deeval
, es.Pero sin él, estás limitado a los límites de un comando simple en virtud de las comillas que utilizas (incluso cuando no lo haces porque
$@
actúa como una comilla al comienzo del proceso cuando el comando se analiza para metacaracteres) . La misma restricción se aplica a las asignaciones y redirecciones de la línea de comandos, que se limitan a la línea de comandos de la función. Pero eso no es gran cosa:Podría tener una
<
entrada o>
salida tan fácilmente redirigida allí como si abriera la tubería.De todos modos, en una forma circular, no hay una forma correcta o incorrecta aquí: cada forma tiene sus usos. Es solo que deberías escribirlo mientras piensas usarlo, y debes saber lo que quieres hacer. Omitiendo las cotizaciones pueden tener un propósito - de lo contrario no habría ser cotizaciones en absoluto - pero si los omite por razones no relacionadas con su propósito, estas a escribir código malo. Haz lo que quieres decir; Intento de todos modos.
fuente