$ ls -l /tmp/test/my\ dir/
total 0
Me preguntaba por qué las siguientes formas de ejecutar el comando anterior fallan o tienen éxito.
$ abc='ls -l "/tmp/test/my dir"'
$ $abc
ls: cannot access '"/tmp/test/my': No such file or directory
ls: cannot access 'dir"': No such file or directory
$ "$abc"
bash: ls -l "/tmp/test/my dir": No such file or directory
$ bash -c $abc
'my dir'
$ bash -c "$abc"
total 0
$ eval $abc
total 0
$ eval "$abc"
total 0
Respuestas:
Esto se ha discutido en una serie de preguntas sobre unix.SE, intentaré recopilar todos los problemas que pueda encontrar aquí. Referencias al final.
Por qué falla
La razón por la que enfrenta esos problemas es la división de palabras y el hecho de que las comillas expandidas a partir de variables no actúan como comillas, sino que son solo caracteres ordinarios.
Los casos presentados en la pregunta:
Aquí,
$abc
se divide, yls
obtiene los dos argumentos"/tmp/test/my
ydir"
(con las comillas al frente del primero y al reverso del segundo):Aquí, se cita la expansión, por lo que se mantiene como una sola palabra. El shell intenta encontrar un programa llamado
ls -l "/tmp/test/my dir"
, espacios y comillas incluidos.Y aquí, solo
$abc
se toma la primera palabra o como argumento-c
, por lo que Bash solo se ejecutals
en el directorio actual. Las otras palabras son argumentos para golpear, y se utilizan para rellenar$0
,$1
etc.Con
bash -c "$abc"
, yeval "$abc"
, hay un paso adicional de procesamiento de shell, que hace que las cotizaciones funcionen, pero también hace que todas las expansiones de shell se procesen nuevamente , por lo que existe el riesgo de ejecutar accidentalmente una expansión de comando a partir de datos proporcionados por el usuario, a menos que sea muy cuidado con las citas.Mejores formas de hacerlo
Las dos mejores formas de almacenar un comando son a) usar una función en su lugar, b) usar una variable de matriz (o los parámetros posicionales).
Usando una función:
Simplemente declare una función con el comando dentro y ejecute la función como si fuera un comando. Las expansiones en los comandos dentro de la función solo se procesan cuando se ejecuta el comando, no cuando está definido, y no necesita citar los comandos individuales.
Usando una matriz:
Las matrices permiten crear variables de varias palabras donde las palabras individuales contienen espacios en blanco. Aquí, las palabras individuales se almacenan como elementos de matriz distintos, y la
"${array[@]}"
expansión expande cada elemento como palabras de shell separadas:La sintaxis es un poco horrible, pero las matrices también le permiten construir la línea de comando pieza por pieza. Por ejemplo:
o mantenga partes de la línea de comando constantes y use la matriz para llenar solo una parte, opciones o nombres de archivo:
La desventaja de los arreglos es que no son una característica estándar, por lo que los shells POSIX simples (como
dash
el predeterminado/bin/sh
en Debian / Ubuntu) no los admiten (pero vea a continuación). Sin embargo, Bash, ksh y zsh sí, por lo que es probable que su sistema tenga algún shell que admita matrices.Utilizando
"$@"
En shells sin soporte para matrices con nombre, aún se pueden usar los parámetros posicionales (la pseudo-matriz
"$@"
) para contener los argumentos de un comando.Los siguientes deben ser bits de script portátiles que hacen el equivalente de los bits de código en la sección anterior. La matriz se reemplaza con
"$@"
la lista de parámetros posicionales. La configuración"$@"
se realiza conset
, y las comillas dobles"$@"
son importantes (esto hace que los elementos de la lista se citen individualmente).Primero, simplemente almacenando un comando con argumentos
"$@"
y ejecutándolo:Configuración condicional de partes de las opciones de línea de comando para un comando:
Solo se usa
"$@"
para opciones y operandos:(Por supuesto,
"$@"
generalmente se rellena con los argumentos del script en sí, por lo que deberá guardarlos en algún lugar antes de volver a utilizarlos"$@"
).¡Cuidado con
eval
!A medida que
eval
introduce un nivel adicional de procesamiento de cotizaciones y expansión, debe tener cuidado con las aportaciones del usuario. Por ejemplo, esto funciona siempre que el usuario no escriba comillas simples:Pero si dan la entrada
'$(uname)'.txt
, su script ejecuta felizmente la sustitución del comando.Una versión con matrices es inmune a eso, ya que las palabras se mantienen separadas durante todo el tiempo, no hay presupuesto u otro procesamiento para el contenido de
filename
.Referencias
fuente
cmd="ls -l $(printf "%q" "$filename")"
. no es bonito, pero si el usuario está empeñado en usar uneval
, ayuda. También es muy útil para enviar el comando aunque las cosas similares, como por ejemplossh foohost "ls -l $(printf "%q" "$filename")"
, o en el espíritu de esta pregunta:ssh foohost "$cmd"
.$ alias abc='ls -l "/tmp/test/my dir"'
La forma más segura de ejecutar un comando (no trivial) es
eval
. Luego puede escribir el comando como lo haría en la línea de comando y se ejecuta exactamente como si acabara de ingresarlo. Pero tienes que citar todo.Caso simple:
caso no tan simple:
fuente
El segundo signo de comillas rompe el comando.
Cuando corro:
Me dio un error.
Pero cuando corro
No hay ningún error en absoluto
No hay forma de solucionar esto en ese momento (para mí), pero puede evitar el error al no tener espacio en el nombre del directorio.
Esta respuesta dice que el comando eval puede usarse para arreglar esto pero no funciona para mí :(
fuente
Si esto no funciona
''
, entonces debe usar``
:Actualizar mejor manera:
fuente
$( command... )
lugar de backticks.¿Qué tal un python3 one-liner?
fuente