web-dev-qa-db-fra.com

Pourquoi bashrc vérifie-t-il si le shell actuel est interactif?

Sur mon installation Arch, /etc/bash.bashrc et /etc/skel/.bashrc contiennent ces lignes:

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

Sur Debian, /etc/bash.bashrc a:

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

Et /etc/skel/.bashrc:

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

Selon man bash, cependant, les shells non interactifs ne lisent même pas ces fichiers:

   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 je comprends bien, le *.bashrc les fichiers ne seront lus que si BASH_ENV est réglé pour pointer vers eux. C'est quelque chose qui ne peut pas arriver par hasard et ne se produira que si quelqu'un a explicitement défini la variable en conséquence.

Cela semble rompre la possibilité d'avoir des scripts source d'un utilisateur .bashrc automatiquement en définissant BASH_ENV, quelque chose qui pourrait être utile. Étant donné que bash ne lira jamais ces fichiers lorsqu'il est exécuté de manière non interactive, sauf indication explicite de le faire, pourquoi la valeur par défaut *bashrc les fichiers ne le permettent pas?

64
terdon

C'est une question que j'allais poster ici il y a quelques semaines. Comme terdon, j'ai compris qu'un .bashrc Est uniquement fourni pour des shells Bash interactifs donc il ne devrait pas être nécessaire que .bashrc Vérifie s'il fonctionne dans un Shell interactif. De façon confuse, toutes les les distributions que j'utilise (Ubuntu, RHEL et Cygwin) ont eu un certain type de vérification (test $- Ou $PS1) pour garantir que le shell actuel est interactif. Je n'aime pas programmation culte de la cargaison alors j'ai commencé à comprendre l'objectif de ce code dans mon .bashrc.

Bash a un étui spécial pour les obus distants

Après avoir étudié le problème, j'ai découvert que shells distants sont traités différemment. Bien que les shells Bash non interactifs n'exécutent pas normalement les commandes ~/.bashrc Au démarrage, un cas spécial est fait lorsque le shell est invoqué par le démon shell distant :

Bash tente de déterminer quand il est exécuté avec son entrée standard connectée à une connexion réseau, comme lorsqu'il est exécuté par le démon Shell distant, généralement rshd, ou le démon Shell sécurisé sshd. Si Bash détermine qu'il est exécuté de cette manière, il lit et exécute les commandes à partir de ~/.bashrc, si ce fichier existe et est lisible. Il ne le fera pas s'il est appelé en tant que sh. L'option --norc Peut être utilisée pour inhiber ce comportement et l'option --rcfile Peut être utilisée pour forcer la lecture d'un autre fichier, mais ni rshd ni sshd invoque généralement le Shell avec ces options ou permet de les spécifier.

Exemple

Insérez ce qui suit au début d'une télécommande .bashrc. (Si .bashrc Provient de .profile Ou .bash_profile, Désactivez-le temporairement pendant le test):

echo bashrc
fun()
{
    echo functions work
}

Exécutez les commandes suivantes localement:

$ ssh remote_Host 'echo $- $0'
bashrc
hBc bash
  • Non i dans $- Indique que le shell est non interactif.
  • Aucun - En tête dans $0 Indique que le shell n'est pas un shell de connexion.

Les fonctions shell définies dans la télécommande .bashrc Peuvent également être exécutées:

$ ssh remote_Host fun
bashrc
functions work

J'ai remarqué que le ~/.bashrc Provient seulement lorsqu'une commande est spécifiée comme argument pour ssh. Cela a du sens: lorsque ssh est utilisé pour démarrer un shell de connexion normal, .profile Ou .bash_profile Sont exécutés (et .bashrc N'est fourni que si cela est explicitement fait par l'un de ces fichiers).

Le principal avantage que je peux voir à avoir .bashrc Sourced lors de l'exécution d'une commande à distance (non interactive) est que les fonctions Shell peuvent être exécutées. Cependant, la plupart des commandes dans un .bashrc Typique ne sont pertinentes que dans un shell interactif, par exemple, les alias ne sont développés que si le shell est interactif.

Les transferts de fichiers à distance peuvent échouer

Ce n'est généralement pas un problème lorsque rsh ou ssh sont utilisés pour démarrer un shell de connexion interactif ou lorsque des shells non interactifs sont utilisés pour exécuter des commandes. Cependant, cela peut être un problème pour des programmes tels que rcp, scp et sftp qui utilisent des shells distants pour le transfert de données.

Il s'avère que le shell par défaut de l'utilisateur distant (comme Bash) est implicitement démarré lors de l'utilisation de la commande scp. Il n'y a aucune mention de cela dans la page de manuel - seulement une mention que scp utilise ssh pour son transfert de données. Cela a pour conséquence que si le .bashrc Contient des commandes qui s'impriment sur la sortie standard, les transferts de fichiers échoueront , par exemple, scp échoue sans erreur .

Voir aussi ce rapport de bogue Red Hat relatif d'il y a 15 ans, scp se casse quand il y a une commande echo dans/etc/bashrc (qui a finalement été fermé par WONTFIX).

Pourquoi scp et sftp échouent

SCP (Secure copy) et SFTP (Secure File Transfer Protocol) ont leurs propres protocoles pour que les extrémités locale et distante échangent des informations sur le ou les fichiers transférés. Tout texte inattendu de l'extrémité distante est (à tort) interprété comme faisant partie du protocole et le transfert échoue. Selon un FAQ du Snail Book

Ce qui se produit souvent, cependant, c'est qu'il y a des instructions dans le système ou dans les fichiers de démarrage Shell par utilisateur sur le serveur (.bashrc, .profile, /etc/csh.cshrc, .login, Etc.) qui produisent des messages texte à la connexion, destinés à être lus par les humains (comme fortune, echo "Hi there!", Etc.).

Un tel code ne devrait produire une sortie sur les connexions interactives que lorsqu'un tty est attaché à l'entrée standard. S'il ne fait pas ce test, il insérera ces SMS là où ils n'appartiennent pas: dans ce cas, polluer le flux de protocole entre scp2/sftp et sftp-server .

La raison pour laquelle les fichiers de démarrage du shell sont pertinents est que sshd utilise le shell de l'utilisateur lors du démarrage de tout programme au nom de l'utilisateur ( en utilisant par exemple/bin/sh -c "commande"). Il s'agit d'une tradition Unix et présente des avantages:

  • La configuration habituelle de l'utilisateur (alias de commande, variables d'environnement, umask, etc.) est en vigueur lorsque des commandes distantes sont exécutées.
  • La pratique courante de définir le shell d'un compte sur/bin/false pour le désactiver empêchera le propriétaire d'exécuter des commandes, si l'authentification réussit encore accidentellement pour une raison quelconque.

Détails du protocole SCP

Pour ceux qui s'intéressent aux détails du fonctionnement de SCP, j'ai trouvé des informations intéressantes dans Comment fonctionne le protocole SCP qui comprend des détails sur Exécuter scp avec des profils Shell bavards sur la télécommande côté? :

Par exemple, cela peut se produire si vous ajoutez ceci à votre profil Shell sur le système distant:

écho ""

Pourquoi ça se bloque? Cela vient de la façon dont scp en mode source attend la confirmation du premier message de protocole. S'il ne s'agit pas d'un 0 binaire, il s'attend à ce qu'il s'agisse d'une notification d'un problème distant et attend que plus de caractères forment un message d'erreur jusqu'à ce que la nouvelle ligne arrive. Comme vous n'avez pas imprimé une autre nouvelle ligne après la première, votre scp local reste simplement dans une boucle, bloqué sur read(2). En attendant, après le traitement du profil Shell sur le côté distant, scp en mode récepteur a été démarré, qui se bloque également sur read(2), en attendant un zéro binaire indiquant le début des données transfert.

Conclusion/TLDR

La plupart des instructions dans un .bashrc Typique ne sont utiles que pour un shell interactif - pas lors de l'exécution de commandes distantes avec rsh ou ssh. Dans la plupart de ces situations, il n'est pas souhaitable de définir des variables Shell, des alias et de définir des fonctions - et l'impression n'importe quel texte à la sortie standard est activement nuisible si le transfert de fichiers à l'aide de programmes tels que scp ou sftp. Quitter après avoir vérifié que le shell actuel n'est pas interactif est le comportement le plus sûr pour .bashrc.

75
Anthony Geoghegan

La page de manuel oublie de mentionner que bash source également .bashrc pour les shells distants non interactifs, comme dans

ssh hostname command

http://git.savannah.gnu.org/cgit/bash.git/tree/Shell.c#n101

 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#n105

          /* 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);
16
Mikel

Par convention, .bashrc est l'endroit où l'utilisateur stocke la configuration personnalisée du Shell.

Ces configurations personnalisées peuvent être des variables d'environnement, des alias, une invite de fantaisie. Avec un shell non interactif, ces petites choses n'ont aucun sens. De plus, un Shell non interactif peut être appelé dans de nombreux contextes, vous n'êtes pas sûr que ces variables d'environnement puissent conduire à des faux négatifs, voire à des vulnérabilités.

Un exemple le plus proche est un alias comme:

alias cp='cp -i'

Ensuite, il bloque votre Shell non interactif pour toujours.

Ainsi, la vérification s'effectue en haut de .bashrc pour éviter tout problème.


Parce que le shell peut être appelé comme non interactif login Shell, donc bloquez explicitement le sourcing *bashrc aucun sens.

Lorsque le shell était appelé shell de connexion non interactif , il source /etc/profile, puis sourcez le premier trouvé dans ~/.bash_profile, ~/.bash_login, et ~/.profile:

Lorsque bash est invoqué en tant que shell de connexion interactif, ou en tant que shell non interactif avec l'option --login , il lit et exécute d'abord les commandes à partir du fichier/etc/profile, si ce fichier existe. Après avoir lu ce fichier, il recherche ~/.bash_profile, ~/.bash_login et ~/.profile, dans cet ordre, et lit et exécute les commandes du premier qui existe et est lisible.

Rien n'empêche ces fichiers de s'approvisionner .bashrc lui-même, faisant ainsi la vérification à l'intérieur .bashrc est plus sûr et simplifie les choses.

5
cuonglm