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

62

Sur mon arche installer, /etc/bash.bashrcet /etc/skel/.bashrccontenir ces lignes:

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

Sur Debian, /etc/bash.bashrca:

# 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, les *.bashrcfichiers ne seront lus que si BASH_ENVest configuré pour les pointer. Ceci 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 empêcher la possibilité que les scripts attribuent .bashrcautomatiquement la source à un utilisateur BASH_ENV, ce qui peut s'avérer utile. Etant donné que bash ne lira jamais ces fichiers lorsqu’ils ne sont pas exécutés de manière interactive, sauf indication contraire explicite, pourquoi les *bashrcfichiers par défaut l’ autorisent-ils?

terdon
la source
5
Pour une réponse pratique, voir SCP échoue sans erreur
«SO, arrête de faire le mal», le

Réponses:

69

C'est une question que j'allais poster ici il y a quelques semaines. Comme terdon , j'ai compris que a .bashrcest uniquement destiné à des shells Bash interactifs; il ne devrait donc pas être nécessaire .bashrcde vérifier s'il tourne dans un shell interactif. De manière confuse, toutes les distributions que j'utilise (Ubuntu, RHEL et Cygwin) avaient un type de vérification (testing $-ou $PS1) pour s'assurer que le shell actuel est interactif. Je n'aime pas la programmation culte de l'industrie du fret, alors je me suis efforcé de comprendre le but de ce code dans mon .bashrc.

Bash a un cas particulier pour les obus distants

Après avoir étudié le problème, j'ai découvert que les coques distantes sont traitées différemment. Bien que les shells non interactifs Bash n'exécutent normalement pas de ~/.bashrccommandes au démarrage, un cas particulier est créé lorsque le shell est appelé par le démon de shell distant :

Bash tente de déterminer quand il est exécuté avec son entrée standard connectée à une connexion réseau, comme si elle était exécutée par le démon de shell distant, généralement rshd, ou par le démon de shell sécurisé sshd. Si Bash détermine qu'il est exécuté de cette manière, il lit et exécute les commandes de ~ / .bashrc, si ce fichier existe et est lisible. Il ne le fera pas s'il est appelé sh. L' --norcoption peut être utilisée pour empêcher ce comportement et l' --rcfileoption pour forcer la lecture d'un autre fichier, mais rshdni sshdgénéralement ni invoquer le shell avec ces options ou permettre leur spécification.

Exemple

Insérez ce qui suit au début d’une télécommande .bashrc. (Si .bashrcprovient de .profileou .bash_profile, désactivez-le temporairement pendant les tests):

echo bashrc
fun()
{
    echo functions work
}

Exécutez les commandes suivantes localement:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • No iin $-indique que le shell n'est pas interactif .
  • Non leader -en $0indique que la coquille n'est pas une coquille de connexion .

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

$ ssh remote_host fun
bashrc
functions work

J'ai remarqué que l' ~/.bashrcon ne sourced lorsqu'une commande est spécifiée comme argument ssh. Cela a du sens: quand sshest utilisé pour démarrer un shell de connexion normal, .profileou .bash_profileest exécuté (et .bashrcn’est recherché que si cela est fait explicitement par l’un de ces fichiers).

Le principal avantage que je peux constater .bashrclors de l’exécution d’une commande à distance (non interactive) est que les fonctions du shell peuvent être exécutées. Cependant, la plupart des commandes d'une commande typique .bashrcne sont pertinentes que dans un shell interactif. Par exemple, les alias ne sont pas développés sauf si le shell est interactif.

Les transferts de fichiers à distance peuvent échouer

Ce n'est généralement pas un problème lorsque rshou sshsont 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, scpet sftpqui utilisent des shells distants pour transférer des données.

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

Reportez-vous également à ce rapport de bogue Red Hat connexe datant d'il y a 15 ans: scp se brise lorsqu'il existe une commande echo dans / etc / bashrc (qui a finalement été fermée en tant que WONTFIX).

Pourquoi scpet sftpéchouer

SCP (copie sécurisée) et SFTP (protocole de transfert de fichier sécurisé) ont leurs propres protocoles pour les extrémités locale et distante afin d’échanger des informations sur le ou les fichiers en cours de transfert. Tout texte inattendu de l'extrémité distante est (à tort) interprété comme faisant partie du protocole et le transfert échoue. Selon une FAQ du livre d'escargot

Ce qui arrive souvent, cependant, est qu'il ya des déclarations contenues dans le système ou shell par l' utilisateur des fichiers de démarrage sur le serveur ( .bashrc, .profile, /etc/csh.cshrc, .login, etc.) des messages texte de sortie sur connexion, destinés à être lus par les humains (comme fortune, echo "Hi there!", etc.).

Un tel code ne doit produire une sortie que sur des connexions interactives, lorsqu'il est ttyassocié à une entrée standard. S'il ne fait pas ce test, il insérera ces messages texte là où ils n'appartiennent pas: dans ce cas, pollue le flux de protocole entre scp2/ sftpet sftp-server.

La raison pour laquelle les fichiers de démarrage du shell sont pertinents, c'est que sshd le shell de l'utilisateur est utilisé lors du démarrage de tout programme pour le compte de l'utilisateur (en utilisant, par exemple, / bin / sh -c "commande"). C’est une tradition Unix qui présente des avantages:

  • La configuration habituelle de l'utilisateur (alias de commande, variables d'environnement, umask, etc.) est effective lors de l'exécution de commandes à distance.
  • La pratique courante consistant à 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éussissait encore par accident pour une raison quelconque.

Détails du protocole SCP

Pour les personnes intéressées par le fonctionnement de SCP, j'ai trouvé des informations intéressantes dans Fonctionnement du protocole SCP, notamment des informations sur Exécution de scp avec des profils de shell parlants du côté distant. :

Par exemple, cela peut arriver si vous ajoutez ceci à votre profil shell sur le système distant:

écho ""

Pourquoi ça pend juste? Cela vient de la manière dont scpen la source en mode attend la confirmation du premier message de protocole. S'il ne s'agit pas de 0 binaire, il s'attend à ce qu'il s'agisse d'une notification d'un problème distant et attend que davantage de caractères forment un message d'erreur jusqu'à ce que la nouvelle ligne arrive. Comme vous n'avez pas imprimé une nouvelle ligne après la première, votre section locale scpreste dans une boucle bloquée read(2). Dans l'intervalle, après le traitement du profil de shell côté distant, le scpmode récepteur a été démarré, mais celui-ci se bloque également read(2), dans l'attente d'un zéro binaire indiquant le début du transfert de données.

Conclusion / TLDR

La plupart des instructions typiques .bashrcne sont utiles que pour un shell interactif - pas lors de l'exécution de commandes distantes avec rshou ssh. Dans la plupart des situations de ce type, il n'est pas souhaitable de définir des variables de shell, des alias et des fonctions, et l'impression de texte sur une sortie standard est dangereuse pour le transfert de fichiers à l'aide de programmes tels que scpou sftp. Quitter après avoir vérifié que le shell actuel est non interactif est le comportement le plus sûr .bashrc.

Anthony G - justice pour Monica
la source
bel article. mais je devrais faire avec ça? Je dois vérifier par .bashrc mais toujours scp sortir avec 0 code et ne rien copier dans la boîte à distance
ses
@ses Il serait préférable de poser une nouvelle question où vous pourrez décrire votre situation plus en détail.
Anthony G - justice pour Monica le
16

La page de manuel omet de mentionner que les bashsources .bashrcpour les shells distants non interactifs, comme dans

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
la source
Sourcez-vous votre .bashrc à partir de votre .bash_profile (ou .profile)?
Glenn Jackman
Oui, mais ce n'est pas la question. Un vide .bash_profileprésenterait le même comportement.
Mikel
Je ne sais pas ce que ça me montre. Voulez-vous dire que rsh(que je n'ai jamais utilisé) des sources ~/.bashrc? Et que, par extension, les distributions Linux le bloquent bashjuste au cas où quelqu'un essaierait de lancer un script avec rsh? ssh serverne devrait pas toucher .bashrccar c'est un shell de connexion de toute façon. Non, sauf si votre système est l'un de ceux d'où provient ~ / .bashrc` ~/.profile. Et ssh server commandne devrait même pas faire ça. Certainement pas sur mon système.
terdon
2
Non, je vous ai lié à la bashsource, pas la rshsource. :) Le commentaire s'applique sshégalement, il a été écrit il y a très longtemps. Voir la réponse mise à jour avec plus de source.
Mikel
Ah, c'est utile, merci. Comment pourrais-je tester cela cependant? J'ai essayé d'ajouter un echoau sommet de /etc/bash.bashrcet ~/.bashrc(avant les tests de shell interactifs), puis ssh dans la machine cible avec ssh server hostnameet pendant l' hostnameexécution, je n'ai vu aucun de mes échos. Est-ce que mon shell_level(quoi que ce soit) n'est pas <2?
terdon
5

Par convention, .bashrcest l'endroit où l'utilisateur stocke la configuration de personnalisation pour le shell.

Ces configurations personnalisées peuvent être des variables d’environnement, des alias, des invites de fantaisie. Avec un shell non interactif, ces choses courtes n'ont pas de 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 cas de faux négatifs, voire à une vulnérabilité de la sécurité.

Un exemple le plus proche est un alias tel que:

alias cp='cp -i'

Ensuite, il suspendra votre shell non interactif pour toujours.

Donc, la vérification s’effectue en haut .bashrcpour s’assurer que nous n’aurons pas de problèmes.


Comme le shell peut être appelé en tant que shell de connexion non interactif , il est donc *bashrcinutile de recourir explicitement à la recherche de sources par blocs .

Lorsque le réservoir a été appelé comme shell de connexion non interactif , sa source /etc/profile, la source puis la première trouvée dans ~/.bash_profile, ~/.bash_loginet ~/.profile:

Lorsque bash est appelé en tant que shell de connexion interactif ou en tant que shell non interactif avec l'option --login , il commence par lire et exécuter les commandes 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, puis lit et exécute les commandes à partir de la première qui existe et est lisible.

Rien n'empêche ces fichiers de .bashrcs'auto- approvisionner . Le contrôle à l'intérieur .bashrcest donc plus sûr et simplifie les choses.

cuonglm
la source
Oui je sais. C'est pourquoi les shells non interactifs ne génèrent aucun *bashrcfichier source . Ma question est la suivante: pourquoi différents fournisseurs de Linux essaient-ils d'empêcher explicitement les shells non interactifs de lire ces fichiers si ces fichiers ne sont de toute façon pas lus par des shells non interactifs?
terdon
1
@terdon: Puisqu'un shell peut être appelé en tant que shell de connexion non interactif , un shell de connexion sera source .bash_profile, qui peut être source .bashrc.
jeudi
Non, dans les shells non interactifs, la seule chose lue est $BASH_ENV. Les deux *profileet *bashrcsont ignorés. Ou du moins, c'est ce que dit la page de manuel. Cependant, comme Mikel l’a montré, la page de manuel pourrait mentir.
terdon
@terdon: Lisez ma réponse mise à jour avec le lien qu'elle contient. Avez-vous essayé bash -l -c :d'appeler un shell de connexion non interactif?
jeudi
Sensationnel. Vous avez absolument raison. J'ai lu cette partie environ --login100 fois mais, apparemment, elle ne s'était jamais enregistrée. Merci!
terdon