run for linux bash shell

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:

  1. echo
  2. "hello
  3. this
  4. is
  5. a
  6. 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 .