GNU/Linux >> Tutoriales Linux >  >> Linux

¿Por qué un shell de inicio de sesión 'sudo -i' interrumpe un argumento de cadena de comando Here-doc?

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 -ca 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).

Relacionado:¿Cómo completar bash para alias de comando?

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

Linux
  1. ¿Por qué sale esta canalización de shell?

  2. Cambiar shell predeterminado en Linux

  3. ¿Qué significa la sintaxis |&en lenguaje shell?

  4. ¿Por qué popen() invoca un shell para ejecutar un proceso?

  5. ¿Por qué ejecutar un comando de shell de Linux con '&'?

Tutorial del comando chsh de Linux para principiantes (5 ejemplos)

¿Por qué la sustitución de comandos de Shell engulle un carácter de nueva línea final?

¿Cómo pasar un argumento de línea de comando a un script de Shell?

¿Por qué los shells interactivos están predeterminados en los shells de inicio de sesión de Osx?

¿Por qué $shlvl comienza en el nivel 2 en los shells sin inicio de sesión pero en el nivel 1 en los shells de inicio de sesión en Rhel 7?

¿Por qué el usuario 'bin' necesita un shell de inicio de sesión?