La mayoría de estas respuestas se refieren al caso específico que está preguntando. Hay un enfoque general que un amigo y yo hemos desarrollado que permite arbitraria citar en caso de que necesite cita fiesta de comandos a través de múltiples capas de desarrollo del forro, por ejemplo, a través de ssh, su -c
, bash -c
, etc. Hay un núcleo primitivo que necesita, aquí en bash nativo:
quote_args() {
local sq="'"
local dq='"'
local space=""
local arg
for arg; do
echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
space=" "
done
}
Esto hace exactamente lo que dice: cita cada argumento individualmente (después de la expansión bash, por supuesto):
$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'
Hace lo obvio para una capa de expansión:
$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2
(Tenga en cuenta que las comillas dobles $(quote_args ...)
son necesarias para convertir el resultado en un argumento único para bash -c
). Y se puede usar de manera más general para citar correctamente a través de múltiples capas de expansión:
$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2
El ejemplo anterior:
- shell-cita cada argumento al interior
quote_args
individualmente y luego combina el resultado resultante en un solo argumento con las comillas dobles internas.
- cotizaciones
bash
, -c
y el resultado ya citado una vez del paso 1, y luego combina el resultado en un solo argumento con las comillas dobles externas.
- envía ese desorden como argumento al exterior
bash -c
.
Esa es la idea en pocas palabras. Puede hacer algunas cosas bastante complicadas con esto, pero debe tener cuidado con el orden de evaluación y las subcadenas citadas. Por ejemplo, lo siguiente hace las cosas mal (para alguna definición de "mal"):
$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure
En el primer ejemplo, bash se expande inmediatamente quote_args cd /; pwd 1>&2
en dos comandos separados quote_args cd /
y pwd 1>&2
, por lo tanto, el CWD aún está en ejecución /tmp
cuando pwd
se ejecuta el comando. El segundo ejemplo ilustra un problema similar para el globbing. De hecho, el mismo problema básico ocurre con todas las expansiones de bash. El problema aquí es que una sustitución de comando no es una llamada a una función: literalmente está evaluando un script bash y usando su salida como parte de otro script bash.
Si intenta simplemente escapar de los operadores de shell, fallará porque la cadena resultante que se pasa bash -c
es solo una secuencia de cadenas entre comillas individuales que no se interpretan como operadores, lo que es fácil de ver si repite la cadena que han pasado a bash:
$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'
El problema aquí es que estás sobre citando. Lo que necesita es que los operadores no estén entre comillas como entrada para el cerramiento bash -c
, lo que significa que deben estar fuera de la $(quote_args ...)
sustitución del comando.
En consecuencia, lo que debe hacer en el sentido más general es citar shell cada palabra del comando que no se pretende expandir en el momento de la sustitución del comando por separado, y no aplicar ninguna cita adicional a los operadores de shell:
$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success
Una vez que haya hecho esto, toda la cadena es un juego justo para seguir citando a niveles arbitrarios de evaluación:
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success
etc.
Estos ejemplos pueden parecer excesivos dado que las palabras como success
, sbin
y pwd
no necesitan ser citadas, pero el punto clave a recordar cuando se escribe un script que toma una entrada arbitraria es que desea citar todo lo que no está absolutamente seguro . No es necesario citar, porque nunca se sabe cuándo un usuario arrojará a Robert'; rm -rf /
.
Para comprender mejor lo que sucede debajo de las cubiertas, puedes jugar con dos pequeñas funciones auxiliares:
debug_args() {
for (( I=1; $I <= $#; I++ )); do
echo -n "$I:<${!I}> " 1>&2
done
echo 1>&2
}
debug_args_and_run() {
debug_args "$@"
"$@"
}
que enumerará cada argumento a un comando antes de ejecutarlo:
$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2