¿Por qué bashrc comprueba si el shell actual es interactivo?

62

En mi instalación de Arch, /etc/bash.bashrcy /etc/skel/.bashrccontiene estas líneas:

# If not running interactively, don't do anything
[[ $- != *i* ]] && return

En Debian, /etc/bash.bashrctiene:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

Y /etc/skel/.bashrc:

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

Según man bash, sin embargo, los shells no interactivos ni siquiera leen estos archivos:

   When  bash  is  started  non-interactively,  to run a shell script, for
   example, it looks for the variable BASH_ENV in the environment, expands
   its  value if it appears there, and uses the expanded value as the name
   of a file to read and execute.  Bash behaves as if the  following  com
   mand were executed:
          if [ -n "$BASH_ENV" ]; then . "$BASH_ENV"; fi
   but  the value of the PATH variable is not used to search for the file
   name.

Si entiendo correctamente, los *.bashrcarchivos solo se leerán si BASH_ENVestá configurado para señalarlos. Esto es algo que no puede suceder por casualidad y solo ocurrirá si alguien ha establecido explícitamente la variable en consecuencia.

Eso parece romper la posibilidad de que las secuencias de comandos obtengan .bashrcautomáticamente a un usuario mediante la configuración BASH_ENV, algo que podría ser útil. Dado que bash nunca leerá estos archivos cuando se ejecute de forma no interactiva a menos que se le indique explícitamente que lo haga, ¿por qué los *bashrcarchivos predeterminados no lo permiten?

terdon
fuente
55
Para una respuesta práctica, vea SCP falla sin error
Gilles 'SO- deja de ser malvado'

Respuestas:

69

Esta es una pregunta que iba a publicar aquí hace unas semanas. Al igual que terdon , entendí que a .bashrcsolo se obtiene para shells interactivos de Bash, por lo que no debería ser necesario .bashrcverificar si se está ejecutando en un shell interactivo. Confusamente, todas las distribuciones que uso (Ubuntu, RHEL y Cygwin) tenían algún tipo de verificación (prueba $-o $PS1) para garantizar que el shell actual sea interactivo. No me gusta la programación de culto de carga, así que empecé a entender el propósito de este código en mi .bashrc.

Bash tiene un estuche especial para proyectiles remotos

Después de investigar el problema, descubrí que los proyectiles remotos se tratan de manera diferente. Si bien los shells Bash no interactivos normalmente no ejecutan ~/.bashrccomandos al inicio, se hace un caso especial cuando el demonio de shell remoto invoca el shell :

Bash intenta determinar cuándo se está ejecutando con su entrada estándar conectada a una conexión de red, como cuando la ejecuta el demonio de shell remoto, generalmente rshd, o el demonio de shell seguro sshd. Si Bash determina que se está ejecutando de esta manera, lee y ejecuta comandos desde ~ / .bashrc, si ese archivo existe y es legible. No hará esto si se invoca como sh. La --norcopción se puede usar para inhibir este comportamiento, y la --rcfileopción se puede usar para forzar la lectura de otro archivo, pero generalmente rshdni sshdinvocar el shell con esas opciones o permitir que se especifiquen.

Ejemplo

Inserte lo siguiente al comienzo de un control remoto .bashrc. (Si .bashrcproviene de .profileo .bash_profile, desactívelo temporalmente durante la prueba):

echo bashrc
fun()
{
    echo functions work
}

Ejecute los siguientes comandos localmente:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • No iin $-indica que el shell no es interactivo .
  • No se lleva -en $0indica que la cáscara no es un shell de entrada .

Las funciones de shell definidas en el control remoto .bashrctambién se pueden ejecutar:

$ ssh remote_host fun
bashrc
functions work

Me di cuenta de que la ~/.bashrcestá solamente proceden cuando se especifica un comando como argumento para ssh. Esto tiene sentido: cuando sshse usa para iniciar un shell de inicio de sesión normal .profileo cuando .bash_profilese ejecuta (y .bashrcsolo se obtiene si uno de estos archivos lo hace explícitamente).

El principal beneficio que puedo ver al haber .bashrcobtenido al ejecutar un comando remoto (no interactivo) es que se pueden ejecutar funciones de shell. Sin embargo, la mayoría de los comandos en un típico .bashrcsolo son relevantes en un shell interactivo, por ejemplo, los alias no se expanden a menos que el shell sea interactivo.

Las transferencias de archivos remotas pueden fallar

Esto no suele ser un problema cuando rsho sshse utiliza para iniciar un intérprete interactivo de ingreso o cuando los proyectiles no interactivas se utilizan para ejecutar comandos. Sin embargo, puede ser un problema para programas como rcp, scpy sftpque usan shells remotos para transferir datos.

Resulta que el shell predeterminado del usuario remoto (como Bash) se inicia implícitamente cuando se usa el scpcomando. No se menciona esto en la página de manual, solo una mención que se scputiliza sshpara la transferencia de datos. Esto tiene la consecuencia de que si .bashrccontiene algún comando que imprima a la salida estándar, las transferencias de archivos fallarán , por ejemplo, scp falla sin error .

Vea también este informe de error relacionado con Red Hat de hace 15 años, scp se rompe cuando hay un comando echo en / etc / bashrc (que finalmente se cerró como WONTFIX).

Por qué scpy sftpfallar

SCP (Copia segura) y SFTP (Protocolo seguro de transferencia de archivos) tienen sus propios protocolos para que los extremos locales y remotos intercambien información sobre los archivos que se transfieren. Cualquier texto inesperado del extremo remoto se interpreta (erróneamente) como parte del protocolo y la transferencia falla. De acuerdo con un FAQ del Snail Book

Lo que sucede a menudo, sin embargo, es que hay declaraciones, ya sea en el sistema de archivos de inicio o la cáscara de cada usuario en el servidor ( .bashrc, .profile, /etc/csh.cshrc, .login, etc.), que los mensajes de texto de salida de entrada, destinado a ser leído por los seres humanos (como fortune, echo "Hi there!", etc.)

Dicho código solo debe producir resultados en inicios de sesión interactivos, cuando hay una ttyentrada estándar adjunta. Si no realiza esta prueba, insertará estos mensajes de texto donde no pertenecen: en este caso, contaminando el flujo de protocolo entre scp2/ sftpy sftp-server.

La razón por la cual los archivos de inicio de la shell son relevantes, es que sshd emplea la shell del usuario al iniciar cualquier programa en nombre del usuario (usando, por ejemplo, / bin / sh -c "comando"). Esta es una tradición de Unix y tiene ventajas:

  • La configuración habitual del usuario (alias de comando, variables de entorno, umask, etc.) tiene efecto cuando se ejecutan comandos remotos.
  • La práctica común de configurar el shell de una cuenta en / bin / false para deshabilitarla evitará que el propietario ejecute comandos, en caso de que la autenticación aún tenga éxito accidentalmente por alguna razón.

Detalles del protocolo SCP

Para aquellos interesados ​​en los detalles de cómo funciona SCP, encontré información interesante en Cómo funciona el protocolo SCP, que incluye detalles sobre cómo ejecutar scp con perfiles de shell habladores en el lado remoto. :

Por ejemplo, esto puede suceder si agrega esto a su perfil de shell en el sistema remoto:

eco ""

¿Por qué simplemente se cuelga? Eso viene de la forma scpen que en modo fuente espera la confirmación del primer mensaje de protocolo. Si no es 0 binario, espera que sea una notificación de un problema remoto y espera a que más caracteres formen un mensaje de error hasta que llegue la nueva línea. Como no imprimiste otra línea nueva después de la primera, tu local scpsolo permanece en un bucle, bloqueado read(2). Mientras tanto, después de que el perfil de shell se procesó en el lado remoto, scpse inició el modo sumidero, que también se bloquea read(2), esperando un cero binario que indique el inicio de la transferencia de datos.

Conclusión / TLDR

La mayoría de las declaraciones en un típico .bashrcsolo son útiles para un shell interactivo, no cuando se ejecutan comandos remotos con rsho ssh. En la mayoría de estas situaciones, no es deseable establecer variables de shell, alias y definir funciones, y la impresión de cualquier texto para estandarizar es activamente perjudicial si se transfieren archivos usando programas como scpo sftp. Salir después de verificar que el shell actual no es interactivo es el comportamiento más seguro para .bashrc.

Anthony G - justicia para Monica
fuente
Buen articulo. pero debería hacer con eso? Tengo registro por .bashrc pero todavía scp sale con código 0 y no copio nada a la caja remota
ses
@ses Sería mejor hacer una nueva pregunta donde pueda describir su situación con mayor detalle.
Anthony G - justicia para Mónica
16

La página de manual no menciona que bashtambién hay fuentes .bashrcpara shells remotos no interactivos, como en

ssh hostname command

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1010

 COMMAND        EXECUTE BASHRC
 --------------------------------
 bash -c foo        NO
 bash foo           NO
 foo                NO
 rsh machine ls     YES (for rsh, which calls 'bash -c')
 rsh machine foo    YES (for shell started by rsh) NO (for foo!)
 echo ls | bash     NO
 login              NO
 bash               YES

http://git.savannah.gnu.org/cgit/bash.git/tree/shell.c#n1050

          /* If we were run by sshd or we think we were run by rshd, execute
             ~/.bashrc if we are a top-level shell. */
          if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2)
            {
              maybe_execute_file (SYS_BASHRC, 1);
              maybe_execute_file (bashrc_file, 1);
Mikel
fuente
¿Obtiene su .bashrc de su .bash_profile (o .profile)?
Glenn Jackman
Sí, pero ese no es el punto. Un vacío .bash_profileexhibiría el mismo comportamiento.
Mikel
No estoy seguro de lo que me está mostrando. ¿Estás diciendo que rsh(que nunca he usado) fuentes ~/.bashrc? ¿Y eso, por extensión, las distribuciones de Linux lo bloquean por bashsi alguien intenta ejecutar un script rsh? ssh serverno debería tocar .bashrcya que de todos modos es un shell de inicio de sesión. No, a menos que su sistema sea uno de esos de donde provenga ~ / .bashrc` ~/.profile. Y ssh server commandni siquiera debería hacer eso. Ciertamente no en mi sistema.
terdon
2
No. Te vinculé a la bashfuente, no a la rshfuente. :) El comentario también se aplica ssh, fue escrito hace mucho tiempo. Ver respuesta actualizada con más fuente.
Mikel
Ah, eso es útil, gracias. ¿Pero cómo probaría esto? Intenté agregar un echoen la parte superior de /etc/bash.bashrcy ~/.bashrc(antes de las pruebas de shell interactivas) y luego ssh en la máquina de destino con ssh server hostnamey mientras hostnameestaba en ejecución, no vi ninguno de mis ecos. ¿Es que mi shell_level(lo que sea que sea) no lo es <2?
terdon
5

Por convención, .bashrces el lugar donde el usuario almacena la configuración personalizada para el shell.

Estas configuraciones personalizadas pueden ser variables de entorno, alias, avisos sofisticados. Con un shell no interactivo, esas cosas sin sentido no tienen sentido. Además, se puede llamar a un shell no interactivo en muchos contextos, no está seguro de que esas variables de entorno puedan conducir a un caso falso negativo o incluso a seguridad vulnerable.

Un ejemplo más cercano es un alias como:

alias cp='cp -i'

Luego cuelga su shell no interactivo para siempre.

Por lo tanto, la verificación se realiza en la parte superior .bashrcpara garantizar que no tengamos problemas.


Debido a que el shell se puede llamar como un shell de inicio de sesión no interactivo , entonces el origen explícito de bloques *bashrcno tiene sentido.

Cuando la cáscara se llama como shell de entrada no interactivo , es fuente /etc/profile, a continuación, la fuente de la primera que se encuentra en ~/.bash_profile, ~/.bash_loginy ~/.profile:

Cuando se invoca bash como un shell de inicio de sesión interactivo o como un shell no interactivo con la opción --login , primero lee y ejecuta comandos del archivo / etc / profile, si ese archivo existe. Después de leer ese archivo, busca ~ / .bash_profile, ~ / .bash_login y ~ / .profile, en ese orden, y lee y ejecuta comandos del primero que existe y es legible.

Nada impide que esos archivos se aprovisionen .bashrc, por lo que hacer la comprobación interna .bashrces más seguro y simplifica las cosas.

Cuonglm
fuente
Si lo sé. Esta es la razón por la cual los shells no interactivos no generan *bashrcarchivos en absoluto. Mi pregunta es por qué varios proveedores de Linux se toman la molestia de bloquear explícitamente que los shells no interactivos lean estos archivos si estos no son leídos por shells no interactivos de todos modos.
terdon
1
@terdon: Debido a que un shell se puede llamar como un shell de inicio de sesión no interactivo , se generará un shell de inicio de sesión .bash_profile, que puede ser source .bashrc.
Cuonglm
No, en shells no interactivos, lo único que se lee es $BASH_ENV. Ambos *profiley *bashrcson ignorados. O, al menos, eso es lo que dice la página del manual. Sin embargo, como Mikel ha mostrado, la página del manual podría estar mintiendo.
terdon
@terdon: lea mi respuesta actualizada con el enlace que contiene. ¿Has intentado bash -l -c :invocar un shell de inicio de sesión no interactivo?
Cuonglm
Guau. Estás absolutamente en lo correcto. He leído esa parte unas --login100 veces pero, aparentemente, nunca se había registrado. ¡Gracias!
terdon