En la secuencia de cinco comandos a continuación, todos dependen de comillas simples para transferir la posible sustitución de variable al llamado bash
shell en lugar del shell que llama. El usuario que llama es xx , pero el shell llamado se ejecutará como usuario yy . El primer comando sustituye $HOME con el valor del shell que llama porque el shell llamado no es un shell de inicio de sesión. El segundo comando sustituye el valor de $HOME cargado por un shell de inicio de sesión, por lo que es el valor que pertenece al usuario yy . El tercer comando no se basa en un valor de $HOME y crea un archivo en el directorio de inicio adivinado del usuario yy .
¿Por qué falla el cuarto comando? La intención es que escriba el mismo archivo, pero basándose en la variable $HOME perteneciente al usuario yy para asegurarse de que realmente termine en su directorio de inicio. No entiendo por qué un shell de inicio de sesión interrumpe el comportamiento de un comando here-doc que se pasa como una cadena estática entre comillas simples. La falla del quinto comando verifica que este problema no se trata de sustitución de variables.
[email protected] ~ $ sudo -u yy bash -c 'echo HOME=$HOME'
HOME=/home/xx
[email protected] ~ $ sudo -iu yy bash -c 'echo HOME=$HOME'
HOME=/home/yy
[email protected] ~ $ sudo -u yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
[email protected] ~ $ sudo -iu yy bash -c 'cat > $HOME/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
[email protected] ~ $ sudo -iu yy bash -c 'cat > /home/yy/test.sh << "EOF"
> script-content
> EOF
> '
bash: warning: here-document at line 0 delimited by end-of-file (wanted `EOFscript-contentEOF')
Estos comandos se emitieron en un sistema Linux Mint 18.3 Cinnamon de 64 bits, que se basa en Ubuntu 16.04 (Xenial Xerus).
Actualización: El aspecto here-doc solo está nublando el problema. He aquí una simplificación del problema:
$ sudo bash -c 'echo 1
> echo 2'
1
2
$ sudo -i bash -c 'echo 1
> echo 2'
1echo 2
¿Por qué el primero de esos dos comandos conserva el salto de línea y el segundo no? sudo
es común a ambos comandos, pero parece estar escapando/filtrando/interpolando de manera diferente dependiendo únicamente de la opción "-i".
Respuesta aceptada:
La documentación para -i
estados:
El -i
(simular inicio de sesión inicial) ejecuta el shell especificado por la entrada de la base de datos de contraseñas del usuario de destino como un shell de inicio de sesión. Esto significa que el shell leerá los archivos de recursos específicos de inicio de sesión, como .profile o .login. Si se especifica un comando, se pasa al shell para su ejecución a través de la opción -c del shell.
Es decir, realmente ejecuta el shell de inicio de sesión del usuario y luego pasa cualquier comando que le haya dado sudo
a él usando -c
– a diferencia qué sudo cmd arg arg
generalmente lo hace sin el -i
opción. Normalmente, sudo
solo usa uno de los exec*
funciona directamente para iniciar el proceso en sí mismo, sin shell intermedio y todos los argumentos se pasan exactamente como están.
Con -i
, configura el entorno, ejecuta el shell del usuario como un shell de inicio de sesión y reconstruye el comando que solicitó ejecutar como argumento para bash -c
. En su caso, ejecuta (digamos, aproximadamente) /bin/bash -c "bash -c ' ... '"
(imagínese citando eso funciona).
El problema radica en cómo sudo
convierte el comando que escribiste en algo que -c
puede tratar , explicado en la siguiente sección. La última sección tiene algunas soluciones posibles, y en el medio hay alguna técnica de depuración y verificación,
¿Por qué sucede esto?
Al pasar el comando a -c
, necesita algo de preprocesamiento para que haga lo correcto, lo que sudo
hace antes de ejecutar el shell. Por ejemplo, si su comando es:
sudo -iu yy echo 'three spaces'
luego, esos espacios deben escaparse para que el comando signifique lo mismo (es decir, para que el único argumento no se divida en dos palabras). Lo que termina ejecutándose es:
/bin/bash -c 'echo three spaces'
Comencemos con su comando simplificado:
sudo bash -c 'echo 1
echo 2'
En este caso, sudo
cambia de usuario, luego ejecuta execvp("bash", ["bash", "-c", "echo 1necho 2"])
(para una sintaxis literal de matriz inventada).
Con -i
:
sudo -i bash -c 'echo 1
echo 2'
en su lugar, cambia de usuario, luego ejecuta execv("/bin/bash", ["-bash", "-c", "bash -c echo\ 1\necho\ 2"])
, donde \
equivale a un literal y
n
es un salto de línea. Ha escapado de los espacios y la nueva línea en su comando principal precediéndolos con barras invertidas.
Es decir, hay un shell de inicio de sesión externo, que tiene dos argumentos:-c
y su comando completo, reconstruido en una forma que se espera que el shell entienda correctamente. Desafortunadamente, no es así. El bash
interno el comando finalmente intenta ejecutarse:
echo 1
echo 2
donde la primera línea física termina con una continuación de línea (barra invertida seguida de nueva línea), que se elimina por completo. La línea lógica es simplemente echo 1echo 2
, que no hace lo que querías.
Hay un argumento de que esto es una falla en sudo
's escapando, dado el comportamiento estándar de los pares de barra invertida-nueva línea. Creo que debería ser seguro dejarlos sin escapar aquí.
Lo mismo sucede con su comando con un documento aquí. Funciona como, más o menos:
/bin/bash -c 'bash -c cat << "EOF"\012script-content\012EOF\012'
donde