Warum prüft bashrc, ob die aktuelle Shell interaktiv ist?

62

In my Arch installieren /etc/bash.bashrcund /etc/skel/.bashrcdiese Zeilen enthalten:

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

Auf Debian /etc/bash.bashrchat:

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

Und /etc/skel/.bashrc:

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

Laut man bashnicht-interaktiven Shells werden diese Dateien jedoch nicht einmal gelesen:

   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.

Wenn ich es richtig verstehe, werden die *.bashrcDateien nur gelesen, wenn sie BASH_ENVdarauf verweisen. Dies kann nicht zufällig geschehen und tritt nur auf, wenn jemand die Variable explizit entsprechend eingestellt hat.

Dies scheint die Möglichkeit zu zerstören, dass Skripte einem Benutzer .bashrcautomatisch eine Quelle geben, indem sie Einstellungen vornehmen BASH_ENV, was sich als nützlich erweisen könnte. Angesichts der Tatsache, dass bash diese Dateien niemals liest, wenn sie nicht interaktiv ausgeführt werden, es sei denn, es wird ausdrücklich dazu aufgefordert, warum lassen die Standarddateien dies *bashrcnicht zu?

terdon
quelle
5
Eine praktische Antwort finden Sie unter SCP schlägt fehlerfrei fehl
Gilles 'SO - hör auf, böse zu sein'

Antworten:

69

Dies ist eine Frage, die ich vor ein paar Wochen hier posten wollte. Wie bei Terdon habe ich verstanden, dass a .bashrcnur für interaktive Bash-Shells bereitgestellt wird , sodass nicht .bashrcüberprüft werden muss, ob es in einer interaktiven Shell ausgeführt wird. Verwirrenderweise hatten alle von mir verwendeten Distributionen (Ubuntu, RHEL und Cygwin) eine Art Prüfung (Test $-oder $PS1), um sicherzustellen, dass die aktuelle Shell interaktiv ist. Ich mag keine Frachtkultprogrammierung, deshalb habe ich mich daran gemacht, den Zweck dieses Codes in meinem zu verstehen .bashrc.

Bash hat einen speziellen Fall für Remote-Shells

Nachdem ich das Problem untersucht hatte, stellte ich fest, dass Remote-Shells unterschiedlich behandelt werden. Während nicht interaktive Bash-Shells normalerweise keine ~/.bashrcBefehle beim Start ausführen, wird ein Sonderfall gemacht, wenn die Shell vom Remote-Shell-Daemon aufgerufen wird :

Bash versucht zu bestimmen, wann es mit seiner Standardeingabe ausgeführt wird, die mit einer Netzwerkverbindung verbunden ist, wie es normalerweise vom Remote-Shell-Dämon rshdoder vom Secure-Shell-Dämon ausgeführt wird sshd. Wenn Bash feststellt, dass es auf diese Weise ausgeführt wird, liest es Befehle von ~ / .bashrc und führt sie aus, sofern diese Datei vorhanden und lesbar ist. Es wird dies nicht tun, wenn es als aufgerufen wird sh. Die --norcOption kann verwendet werden , um dieses Verhalten zu hemmen, und die --rcfileOption verwendet werden kann , eine andere Datei zu zwingen , zu lesen, aber weder rshdnoch im sshdAllgemeinen die Schale mit diesen Optionen aufrufen oder es ihnen ermöglichen, festgelegt werden.

Beispiel

Fügen Sie am Anfang einer Fernbedienung Folgendes ein .bashrc. (Wenn .bashrcvon .profileoder bezogen .bash_profile, deaktivieren Sie dies vorübergehend während des Tests):

echo bashrc
fun()
{
    echo functions work
}

Führen Sie die folgenden Befehle lokal aus:

$ ssh remote_host 'echo $- $0'
bashrc
hBc bash
  • No iin $-gibt an, dass die Shell nicht interaktiv ist .
  • Keine führenden -in $0zeigt an, dass die Schale nicht um ein Login - Shell .

In der Fernbedienung definierte Shell-Funktionen .bashrckönnen auch ausgeführt werden:

$ ssh remote_host fun
bashrc
functions work

Ich bemerkte , dass das ~/.bashrcist nur bezogen , wenn ein Befehl für als Argument angegeben wird ssh. Dies ist sinnvoll: wenn sshzum Starten einer regulären Anmeldeshell verwendet .profileoder .bash_profileausgeführt wird (und .bashrcnur dann, wenn dies ausdrücklich durch eine dieser Dateien erfolgt).

Der Hauptvorteil, den ich .bashrcbeim Ausführen eines (nicht interaktiven) Remote-Befehls habe, ist, dass Shell-Funktionen ausgeführt werden können. Die meisten Befehle in einem Typical .bashrcsind jedoch nur in einer interaktiven Shell relevant, z. B. werden Aliase nur dann erweitert, wenn die Shell interaktiv ist.

Remote-Dateiübertragungen können fehlschlagen

Dies ist normalerweise kein Problem , wenn rshoder sshverwendet , um ein interaktiv Login - Shell oder wenn nicht-interaktiver Shells zu beginnen , wird verwendet , um Befehle auszuführen. Aber es kann ein Problem sein für Programme wie rcp, scpund sftpdass verwenden Remote - Schalen für die Datenübertragung.

Es stellt sich heraus, dass die Standard-Shell des Remote-Benutzers (wie Bash) implizit gestartet wird, wenn der scpBefehl verwendet wird. Es gibt keine Erwähnung in der Manpage - nur eine Erwähnung, scpdie sshfür die Datenübertragung verwendet wird. Dies hat zur Folge, dass die .bashrcDateiübertragung fehlschlägt , wenn der Befehl Befehle enthält, die an die Standardausgabe gesendet werden. Beispielsweise schlägt scp fehlerfrei fehl .

Siehe auch diesen verwandten Red Hat-Fehlerbericht von vor 15 Jahren, scp bricht ab, wenn es einen Echo-Befehl in / etc / bashrc gibt (der schließlich geschlossen wurde als WONTFIX).

Warum scpund sftpscheitern

SCP (Secure Copy) und SFTP (Secure File Transfer Protocol) haben ihre eigenen Protokolle für die lokale und die entfernte Seite, um Informationen über die übertragenen Dateien auszutauschen. Unerwarteter Text vom entfernten Ende wird (falsch) als Teil des Protokolls interpretiert und die Übertragung schlägt fehl. Laut FAQ aus dem Schneckenbuch

Was oft geschieht, ist jedoch, dass es Aussagen entweder im System oder pro-Benutzer - Shell - Startdateien auf dem Server ( .bashrc, .profile, /etc/csh.cshrc, .loginusw.) , die Ausgabe von Textnachrichten auf Login, soll durch den Menschen gelesen werden (wie fortune, echo "Hi there!", usw.).

Solcher Code sollte nur bei interaktiven Anmeldungen ausgegeben werden, wenn eine ttyStandardeingabe angehängt ist. Wenn dieser Test nicht durchgeführt wird, werden diese Textnachrichten dort eingefügt, wo sie nicht hingehören. In diesem Fall wird der Protokolldatenstrom zwischen scp2/ sftpund verschmutzt sftp-server.

Der Grund, warum die Shell-Startdateien überhaupt relevant sind, ist, dass sshd die Shell des Benutzers verwendet wird, wenn Programme im Namen des Benutzers gestartet werden (z. B. mit / bin / sh -c "Befehl"). Dies ist eine Unix-Tradition und hat Vorteile:

  • Die üblichen Einstellungen des Benutzers (Befehls-Aliase, Umgebungsvariablen, umask usw.) werden wirksam, wenn Remote-Befehle ausgeführt werden.
  • Die übliche Vorgehensweise, die Shell eines Kontos zum Deaktivieren auf / bin / false zu setzen, verhindert, dass der Besitzer Befehle ausführt, sollte die Authentifizierung aus irgendeinem Grund dennoch versehentlich erfolgreich sein.

Details zum SCP-Protokoll

Für diejenigen, die sich für die Details der Funktionsweise von SCP interessieren, fand ich interessante Informationen unter Funktionsweise des SCP-Protokolls , einschließlich Details zum Ausführen von scp mit gesprächigen Shell-Profilen auf der Remote-Seite. :

Dies kann beispielsweise passieren, wenn Sie Folgendes zu Ihrem Shell-Profil auf dem fernen System hinzufügen:

Echo ""

Warum hängt es nur? Das kommt von der Art und Weise , wie scpin Quelle - Modus wartet auf die Bestätigung der ersten Protokollnachricht. Wenn es sich nicht um eine binäre 0 handelt, wird erwartet, dass es sich um eine Benachrichtigung über ein Remote-Problem handelt, und es wird darauf gewartet, dass weitere Zeichen eine Fehlermeldung bilden, bis die neue Zeile eintrifft. Da Sie nach der ersten Zeile keine weitere neue Zeile gedruckt haben, scpbleibt Ihre lokale Zeile in einer Schleife und ist blockiert read(2). In der Zwischenzeit wurde nach der Verarbeitung des Shell-Profils auf der Remote-Seite der scpSink-Modus gestartet, der ebenfalls blockiert read(2)und auf eine binäre Null wartet, die den Beginn der Datenübertragung angibt.

Fazit / TLDR

Die meisten Anweisungen in einem Typical .bashrcsind nur für eine interaktive Shell nützlich - nicht, wenn Remotebefehle mit rshoder ausgeführt werden ssh. In den meisten solchen Situationen Setzen von Shell - Variablen, Aliase und Funktionen definieren , nicht erwünscht ist - und Druck eines beliebigen Textes an der Standardausgabe ist aktiv schädlich , wenn Dateien mit Programmen wie die Übertragung scpoder sftp. Das Beenden nach der Überprüfung, ob die aktuelle Shell nicht interaktiv ist, ist das sicherste Verhalten für .bashrc.

Anthony G - Gerechtigkeit für Monica
quelle
schöner Artikel. aber ich sollte damit machen? Ich habe einchecken durch Bashrc aber immer noch Ausgang scp mit 0 Code und nichts zu Remote - Box kopieren
ses
@ses Am besten stellen Sie eine neue Frage, in der Sie Ihre Situation genauer beschreiben können.
Anthony G - Gerechtigkeit für Monica
16

Die Manpage vernachlässigt zu erwähnen, dass bashauch Quellen .bashrcfür nicht interaktive Remote-Shells wie in

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
quelle
Beziehen Sie Ihre .bashrc-Datei aus Ihrem .bash_profile (oder .profile)?
Glenn Jackman
Ja, aber darum geht es nicht. Eine leere .bash_profilewürde das gleiche Verhalten aufweisen.
Mikel
Ich bin mir nicht sicher, was mir das zeigt. Sagst du das rsh(was ich nie benutzt habe) Quellen ~/.bashrc? Und dass Linux-Distributionen es generell blockieren, bashnur für den Fall, dass jemand jemals versucht, ein Skript damit auszuführen rsh? ssh serversollte nicht anfassen, .bashrcda dies sowieso eine Login-Shell ist. Es sei denn, Ihr System ist eines der Systeme, aus denen ~ / .bashrc` stammt ~/.profile. Und ssh server commandsollte das nicht einmal tun. Auf meinem System sicherlich nicht.
Terdon
2
Ich habe Sie mit der bashQuelle verbunden, nicht mit der rshQuelle. :) Der Kommentar gilt sshauch für, er wurde erst vor sehr langer Zeit geschrieben. Siehe aktualisierte Antwort mit mehr Quelle.
Mikel
Ah, das ist hilfreich, danke. Wie würde ich das allerdings testen? Ich habe versucht, eine echoganz oben in /etc/bash.bashrcund ~/.bashrc(vor den interaktiven Shell-Tests) einzufügen und dann ssh in den Zielcomputer mit ssh server hostnameund während hostnameder Ausführung habe ich keines meiner Echos gesehen. Ist es das meine shell_level(was auch immer das ist) nicht <2?
Terdon
5

Konventionell .bashrcist der Ort, an dem der Benutzer die angepasste Konfiguration für die Shell speichert.

Diese benutzerdefinierten Konfigurationen können Umgebungsvariablen, Aliase und ausgefallene Eingabeaufforderungen sein. Bei einer nicht interaktiven Shell sind diese kurzen Dinge bedeutungslos. Darüber hinaus kann eine nicht interaktive Shell in vielen Kontexten aufgerufen werden. Sie sind sich nicht sicher, ob diese Umgebungsvariablen zu falsch negativen Groß- und Kleinschreibung oder sogar zu Sicherheitslücken führen können.

Ein naheliegendes Beispiel ist ein Alias ​​wie:

alias cp='cp -i'

Dann bleibt Ihre nicht interaktive Shell für immer hängen.

Also führen Sie die Überprüfung oben durch, .bashrcum sicherzustellen, dass wir keine Probleme bekommen.


Da die Shell als nicht interaktive Anmeldeshell aufgerufen werden kann , ist ein explizites Block-Sourcing *bashrcnicht sinnvoll.

Wenn die Schale wurde als nicht-interaktive Login - Shell genannt , es Quelle /etc/profile, Quelle dann die erste gefunden ~/.bash_profile, ~/.bash_loginund ~/.profile:

Wenn bash als interaktive Anmeldeshell oder als nicht interaktive Shell mit der Option --login aufgerufen wird , werden zuerst Befehle aus der Datei / etc / profile gelesen und ausgeführt, sofern diese Datei vorhanden ist. Nach dem Lesen dieser Datei sucht sie in dieser Reihenfolge nach ~ / .bash_profile, ~ / .bash_login und ~ / .profile und liest und führt Befehle von der ersten Datei aus, die vorhanden und lesbar ist.

Nichts hindert diese Dateien daran, sich .bashrcselbst zu beschaffen , so dass die Prüfung im Inneren .bashrcsicherer und einfacher ist.

cuonglm
quelle
Ja, ich weiß, dass. Aus diesem Grund geben nicht interaktive Shells überhaupt keine Quelldateien aus *bashrc. Meine Frage ist, warum verschiedene Linux-Anbieter sich die Mühe machen, nicht interaktive Shells explizit für das Lesen dieser Dateien zu sperren, wenn diese Dateien ohnehin nicht von nicht interaktiven Shells gelesen werden.
Terdon
1
@terdon: Da eine Shell als nicht interaktive Anmeldeshell aufgerufen werden kann , wird eine Anmeldeshell als Quelle verwendet .bash_profile, die Quelle sein kann .bashrc.
Cuonglm
Nein, in nicht interaktiven Shells wird nur Folgendes gelesen $BASH_ENV. Beides *profileund *bashrcwerden ignoriert. Zumindest steht das auf der Manpage. Wie Mikel jedoch gezeigt hat, könnte die Manpage lügen.
Terdon
@terdon: Lies meine aktualisierte Antwort mit dem Link darin. Haben Sie versucht bash -l -c :, eine nicht interaktive Anmeldeshell aufzurufen?
Sonntag,
Beeindruckend. Du hast absolut recht. Ich habe diesen Teil ungefähr --login100 Mal gelesen, aber anscheinend hatte er sich nie registriert. Vielen Dank!
Terdon