run - windows subsystem for linux
Bash insertando comillas en la cadena antes de la ejecuciĆ³n (2)
He logrado rastrear un problema extraño en un script de inicio en el que estoy trabajando. He simplificado el problema en el siguiente ejemplo:
> set -x # <--- Make Bash show the commands it runs
> cmd="echo /"hello this is a test/""
+ cmd=''echo "hello this is a test"''
> $cmd
+ echo ''"hello'' this is a ''test"'' # <--- Where have the single quotes come from?
"hello this is a test"
¿Por qué bash inserta esas comillas extra en el comando ejecutado?
Las citas adicionales no causan ningún problema en el ejemplo anterior, pero realmente me están dando un dolor de cabeza.
Para los curiosos, el código de problema real es:
cmd="start-stop-daemon --start $DAEMON_OPTS /
--quiet /
--oknodo /
--background /
--make-pidfile /
$* /
--pidfile $CELERYD_PID_FILE
--exec /bin/su -- -c /"$CELERYD $CELERYD_OPTS/" - $CELERYD_USER"
Lo que produce esto:
start-stop-daemon --start --chdir /home/continuous/ci --quiet --oknodo --make-pidfile --pidfile /var/run/celeryd.pid --exec /bin/su -- -c ''"/home/continuous/ci/manage.py'' celeryd -f /var/log/celeryd.log -l ''INFO"'' - continuous
Y por lo tanto:
/bin/su: invalid option -- ''f''
Nota: Estoy usando el comando su
aquí ya que necesito asegurarme de que virtualenv del usuario esté configurado antes de ejecutar celeryd. --chuid
no proporcionará esto
Porque cuando intentas ejecutar tu comando con
$cmd
solo una capa de expansión ocurre. $cmd
contiene echo "hello this is a test"
, que se expande en 6 fichas separadas por espacios en blanco:
-
echo
-
"hello
-
this
-
is
-
a
-
test"
y eso es lo que le muestra la salida set -x
: está poniendo comillas simples alrededor de los tokens que contienen comillas dobles, para que quede claro qué son los tokens individuales.
Si desea que $cmd
se expanda en una cadena que luego tenga todas las reglas de cotización de bash nuevamente aplicadas, intente ejecutar su comando con:
bash -c "$cmd"
o (como @bitmask señala en los comentarios, y esto es probablemente más eficiente)
eval "$cmd"
en lugar de solo
$cmd
Use los arrays Bash para lograr el comportamiento que desea, sin tener que recurrir a los muy peligrosos (ver más abajo) eval
y bash -c
.
Usando matrices:
declare -a CMD=(echo --test-arg /"Hello/ there/ friend/")
set -x
echo "${CMD[@]}"
"${CMD[@]}"
salidas:
+ echo echo --test-arg ''"Hello there friend"''
echo --test-arg "Hello there friend"
+ echo --test-arg ''"Hello there friend"''
--test-arg "Hello there friend"
Tenga cuidado de asegurarse de que la invocación de su matriz esté envuelta entre comillas dobles; de lo contrario, Bash intenta realizar la misma "seguridad mínima" escapando de los caracteres especiales:
declare -a CMD=(echo --test-arg /"Hello/ there/ friend/")
set -x
echo "${CMD[@]}"
${CMD[@]}
salidas:
+ echo echo --test-arg ''"Hello there friend"''
echo --test-arg "Hello there friend"
+ echo --test-arg ''"Hello'' there ''friend"''
--test-arg "Hello there friend"
ASIDE: ¿Por qué es peligrosa la eval
?
eval
es solo seguro si puede garantizar que cada entrada que se le pase no cambiará inesperadamente la forma en que funciona el comando bajo eval
.
Ejemplo : como un ejemplo totalmente diseñado, digamos que tenemos un script que se ejecuta como parte de nuestro proceso de implementación de código automatizado. La secuencia de comandos ordena algunas entradas (en este caso, tres líneas de texto codificado) y envía el texto ordenado a un archivo cuyo nombre se basa en el nombre del directorio actual. Similar a la pregunta original de SO planteada aquí, queremos construir dinámicamente el parámetro --output=
pasado para ordenar, pero debemos (no? Realmente) confiar en eval
debido a la función de "seguridad" de auto cotización de Bash.
echo $''3/n2/n1'' | eval sort -n --output="$(pwd | sed ''s:.*/::'')".txt
Al ejecutar este script en el directorio /usr/local/deploy/project1/
un nuevo archivo en /usr/local/deploy/project1/project1.txt
.
De alguna manera, si un usuario creara un subdirectorio de proyecto llamado owned.txt; touch hahaha.txt; echo
owned.txt; touch hahaha.txt; echo
owned.txt; touch hahaha.txt; echo
, la secuencia de comandos ejecutaría la siguiente serie de comandos:
echo $''3/n2/n1''
sort -n --output=owned.txt; touch hahaha.txt; echo .txt
Como puedes ver, eso no es totalmente lo que queremos. Pero puede preguntar, en este ejemplo artificial, no es improbable que el usuario pueda crear un directorio de proyecto owned.txt; touch hahaha.txt; echo
owned.txt; touch hahaha.txt; echo
owned.txt; touch hahaha.txt; echo
, y si pudieran, ¿no estamos ya en problemas?
Tal vez, pero ¿qué pasa con un escenario en el que el script analiza no el nombre del directorio actual, sino el nombre de una rama remota de repositorio de código fuente de git
? A menos que piense ser extremadamente diligente en restringir o desinfectar cada artefacto controlado por el usuario cuyo nombre, identificador u otro tipo de información use su script, manténgase alejado de la eval
.