¿Cuál es la forma correcta de cotizar $ (comando $ arg)?

17

Ya es hora de resolver este enigma que me ha estado molestando durante años ...

Me he encontrado con esto de vez en cuando y pensé que este es el camino a seguir:

$(comm "$(arg)")

Y pensé que mi punto de vista estaba fuertemente respaldado por la experiencia. Pero ya no estoy tan seguro. Shellcheck tampoco puede decidirse . Son ambos:

"$(dirname $0)"/stop.bash
           ^-- SC2086: Double quote to prevent globbing and word splitting.

Y:

$(dirname "$0")/stop.bash
^-- SC2046: Quote this to prevent word splitting.

¿Cuál es la lógica detrás?

(Es Shellcheck versión 0.4.4, por cierto).

Tomász
fuente
1
Citar todas las sustituciones.
Ignacio Vázquez-Abrams
3
Al igual que este: "$(dirname "$0")"/stop.bash? Parece funcionar ... ¿Cuál es la historia?
Tomasz
1
bash es lo suficientemente inteligente como para saber cuándo ha cambiado el contexto.
Ignacio Vazquez-Abrams
1
piénselo de esta manera: shell_level_1 $(shell_level_2) shell_level_1cuando está dentro del $(....), ingresa un "subnivel" de shell, PERO puede escribir en él como si estuviera en el nivel primario (es decir, puede escribir directamente en "lugar de \", etc.). ej .: touch "/tmp/a file" ; echo "its size is: $(find "/tmp/a file" -ls | awk '{print $5}) ..." : if you used backticks you'd have to encontrar \ "/ tmp / un archivo \" `y print \$5. Sin $(...)necesidad: el shell se adapta al nuevo nivel y puede escribir directamente como si su intérprete ahora también estuviera en ese nivel.
Olivier Dulac
"Shellcheck tampoco puede decidirse". - Las dos carreras tienen dos mensajes diferentes y apuntan a lugares diferentes. Quiere los dos.
Izkata

Respuestas:

45

Necesitas usar "$(somecmd "$file")".

Sin las comillas, una ruta con un espacio se dividirá en el argumento somecmdy se dirigirá al archivo incorrecto. Entonces necesitas citas en el interior.

Cualquier espacio en la salida de somecmdtambién causará división, por lo que necesita comillas en el exterior de toda la sustitución del comando.

Las citas dentro de la sustitución del comando no tienen efecto en las citas fuera de ella. El propio manual de referencia de Bash no es muy claro al respecto, pero BashGuide lo menciona explícitamente . El texto en POSIX también lo requiere, ya que "cualquier script de shell válido" está permitido dentro $(...):

Con el $(command)formulario, todos los caracteres que siguen el paréntesis abierto al paréntesis de cierre coincidente constituyen el comando. Se puede usar cualquier script de shell válido para el comando, excepto un script que consista únicamente en redirecciones que produzcan resultados no especificados.


Ejemplo:

$ file="./space here/foo"

a. Sin comillas, dirnameprocesa ambos ./spacey here/foo:

$ printf "<%s>\n" $(dirname $file)
<.>
<here>

si. Citas en el interior, dirnameprocesos ./space here/foo, donaciones ./space here, que se divide en dos:

$ printf "<%s>\n" $(dirname "$file")
<./space>
<here>

C. Citas en el exterior, dirnameprocesa ambos ./spacey here/foo, sale en líneas separadas, pero ahora las dos líneas forman un solo argumento :

$ printf "<%s>\n" "$(dirname $file)"
<.
here>

re. Citas tanto dentro como fuera, esto da la respuesta correcta:

$ printf "<%s>\n" "$(dirname "$file")"
<./space here>

(eso posiblemente hubiera sido más simple si dirnamesolo procesara el primer argumento, pero eso no mostraría la diferencia entre los casos ayc).

Tenga en cuenta que con dirname(y posiblemente otros) también necesita agregar --, para evitar que el nombre de archivo se tome como una opción en caso de que comience con un guión, así que úselo "$(dirname -- "$file")".

ilkkachu
fuente
16
Nota: en general, las comillas no anidan en bash; pero en este caso, $( )crea un nuevo contexto, por lo que las citas dentro de él son independientes de las citas fuera de él. Y generalmente quieres citas en ambos contextos.
Gordon Davisson