
Ich habe ein Skript geschrieben, das ich zunächst starten möchte, ssh-agent
um den Agenten im Hintergrund auszuführen und die entsprechenden Umgebungsvariablen für die aktuelle Shell-Instanz festzulegen. Im zweiten Teil des Skripts möchte ich jedoch auch meinen privaten SSH-Schlüssel hinzufügen, um eine Verbindung zu meinem Server herzustellen.
Derzeit funktioniert keiner der Befehle im Skript mit den anderen. Kann mir jemand helfen, herauszufinden, was ich falsch mache?
#!/bin/bash
exec ssh-agent bash
sleep 5s
ssh-add /media/MyUSB/.ssh/id_00123 &
Darüber hinaus kann ich beim Verwenden des integrierten Debuggers bash
sehen, dass nur der erste Abschnitt des Skripts funktioniert (d. h. exec ssh-agent bash
).
Antwort1
So viele interessante Aspekte in einem so kleinen Skript.
Verständnisssh-agent
Beginnen wir mit dem, was ssh-agent
es tun soll. Sie führen es aus, ssh-agent
wenn Sie einen Prozess wollen, der dort sitzt und auf einem Socket lauscht (das ist eine ArtDateifür bidirektionale Interprozesskommunikation) und bedient Anfragen von Programmen wie ssh-add
oder ssh
, die sich mit dem Socket verbinden. Programme kommunizieren mit dem Agenten und speichern, bearbeiten oder verwenden private Schlüssel.
Jedes Programm, das einen Agenten verwenden möchte, muss den Pfad zum Socket kennen, auf dem der Agent lauscht. Wenn ein Programm den Pfad kennt, kann es den Socket verwenden, um mit dem Agenten zu kommunizieren.
Es wurde einmal eine Designentscheidung getroffen: Jedes Programm, das den Pfad zum Socket eines Authentifizierungsagenten wissen möchte, sollte die SSH_AUTH_SOCK
Variable in seiner eigenen Umgebung überprüfen, der Wert der Variablen ist der Pfad. Es war eine Entscheidung (ich meine, die Dinge könnten auch anders gestaltet werden, z. B. könnten Programme so gestaltet werden, dass sie diesen Pfad jedes Mal über Befehlszeilenargumente akzeptieren), aber es war eine sehr gute Entscheidung.
Das war eine sehr gute Entscheidung, da die Umgebung standardmäßig übernommen wird. Das bedeutet, dass Sie die SSH_AUTH_SOCK
Umgebungsvariable für einen Prozess (z. B. eine Shell) festlegen müssen und alle seine Nachkommen sie übernehmen (es sei denn, einige von ihnen entscheiden sich bewusst dafür, ihre Umgebung zu ändern oder ein Kind mit geänderter Umgebung zu erstellen). Zum Vergleich: Wenn Sie den Pfad jedes Mal als Befehlszeilenargument übergeben, wenn Sie etwas ausführen möchten, das mit dem Agenten kommunizieren soll, ist zusätzliche Tipparbeit erforderlich. Außerdem möchten Sie den Pfad irgendwo speichern, also wahrscheinlich sowieso in einer Variablen. Also los geht‘s: Der Name der Variablen ist standardisiert und interessierte Programme überprüfen ihn automatisch.
Eine andere Möglichkeit war, den Pfad in einer Textdatei an einem festen Ort zu speichern oder sogar von vornherein einen Socket an einem festen Ort zu erstellen. Aber manchmal möchten Sie, dass einige Programme einen Agenten (einen Socket) und andere Programme einen anderen Agenten (einen anderen Socket) verwenden. Es ist schwierig, zwei Programmen zu ermöglichen, unterschiedliche Dateien am gleichen Ort anzuzeigen. Es ist einfach, zwei Programmen zu ermöglichen, unterschiedliche Umgebungsvariablen anzuzeigen.
Interessierte Programme sollten also SSH_AUTH_SOCK
in ihrer Umgebung nachschauen. Wie können wir oder sonst wie diese Variable in der Umgebung eines Prozesses auf den richtigen Wert setzen? Ohne Debugger gibt es zwei Möglichkeiten:
Entweder kennt das übergeordnete Element den Wert und richtet beim Erzeugen eines untergeordneten Elements
SSH_AUTH_SOCK
den richtigen Wert in der Umgebung für das untergeordnete Element ein (eine unveränderte ÜbernahmeSSH_AUTH_SOCK
vom übergeordneten Element kann so interpretiert werden, dass „das übergeordnete Element dies einrichtet, indem es nichts tut“);oder der Prozess lernt den Wert auf andere Weise und ändert seine eigene Umgebung.
Daher ssh-agent
werden zwei Startmethoden unterstützt:
-
ssh-agent command …
Hier
ssh-agent
wird ein Socket erstellt und darauf vorbereitet, zukünftige Programme zu bedienen, die sich mit dem Socket verbinden. Dann wird escommand …
als sein Kind ausgeführt, mitSSH_AUTH_SOCK
dem richtigen Wert in der Umgebung für das Kind. Das Kind (oder jeder Nachkomme, der die Variable erbt) kann den Socket problemlos finden, andere Prozesse jedoch nicht so leicht. Wenn beendet wirdcommand
, wird dies auch getanssh-agent
(selbst wenn es Enkelkinder gibt). -
ssh-agent # but don't use it exactly this way
Hier
ssh-agent
wird in den Hintergrund verzweigt, d. h. es erstellt eine untergeordnete Kopie von sich selbst und wartet nicht, bis diese beendet wird. Das untergeordnete Element löst sich von den Standard-Streams des übergeordneten Elements und vom Terminal und wird nicht von selbst beendet. Das untergeordnete Element ist der eigentliche Agent, der bleibt. Das übergeordnete Element wird von selbst beendet, aber bevor dies geschieht, wird Shell-Code gedruckt. Wenn der Shell-Code von einer Shell ausgewertet wird, veranlasst diese die Shell, ihre eigene Umgebung zu ändern, sodassSSH_AUTH_SOCK
dort der richtige Wert eingefügt wird.Aber die Schale mussauswertendie Ausgabe, nicht nur ausführenssh-agent
, also ist die richtige Vorgehensweise:eval "$(ssh-agent)"
Danach
eval
hat die ausgeführte Shell die richtige Variable (eigentlich: Variablen) in ihrer Umgebung und von nun an werden Befehle wie „ssh-add
Ausführen“ von dieser Shell den Agenten finden, da sie die Variable erben. Das Beenden der Shell beendet den Agenten nicht, daher möchten Sie möglicherweise irgendwann vor dem Beenden der Shell Folgendes aufrufenssh-agent -k
(oder, wenn Sie auch die Variablen zurücksetzen möchten:eval "$(ssh-agent -k)"
). Ein Agent, für den es keinen Prozess mit dem richtigen Wert gibt,SSH_AUTH_SOCK
ist praktisch nutzlos.
Was ist falsch an deinem Skript
Und nun – endlich – zu Ihrem Skript. Dies ist Ihr Skript:
#!/bin/bash exec ssh-agent bash sleep 5s ssh-add /media/MyUSB/.ssh/id_00123 &
Das erste, was das Skript tut, ist exec ssh-agent bash
. exec
weist die Shell, die das Skript interpretiert, an, sich selbst durch den Befehl zu ersetzen, der lautet ssh-agent bash
. Die Shell tut dies und wird , ssh-agent
wodurch ein neues gestartet wird bash
(es ist die Methode 1 von oben). Dies bash
enthält den richtigen Wert von SSH_AUTH_SOCK
, es ist interaktiv, es druckt eine Eingabeaufforderung und ermöglicht Ihnen, Befehle auszuführen (einschließlich Befehle, die benötigen SSH_AUTH_SOCK
). Wenn Ihre ursprüngliche interaktive Shell war, bash
übersehen Sie möglicherweise die Tatsache, dass Sie sich jetzt in einem separaten befinden bash
. Sie können die Existenz von SSH_AUTH_SOCK
als Bestätigung interpretieren, dass ssh-agent
die Umgebung Ihrer ursprünglichen Shell geändert wurde. Nein, Sie befinden sich noch mitten in Ihrem Skript.
Nun, nicht genau in der Mitte. Wenn Sie dies beenden bash
, sleep
werden und der Rest nicht ausgeführt, da die Shell, die das Skript interpretiert, sich selbst durch ersetzt hat ssh-agent
. In gewisser Weise sind Sie eins exit
vor dem Ende des Skripts.
Wenn Ihre Methode zum Ausführen des Skripts wie folgt war ./myscript
, exit
werden Sie zurück in die ursprüngliche Shell gebracht. Wenn Ihre Methode wie folgt war. ./myscript
odersource myscript
dann exit
verhält es sich so, als ob Sie die ursprüngliche Shell beendet hätten, weil die ursprüngliche Shell die Shell war, die das Skript interpretiert hat, und sich selbst durch die Shell ersetzt hat, ssh-agent
die gerade beendet wird, wenn Sie exit
die aktuelle Shell verlassen. Dadurch kann der Eindruck verstärkt werden, dass Sie sich in der ursprünglichen Shell befunden haben (und diese nun beenden).
Die Reparatur
In der Frage hast Du Dein Ziel explizit benannt:
[…] entsprechende Umgebungsvariablen für die aktuelle Shell-Instanz. […]
Um die Umgebung der aktuellen Shell zu ändern, muss das Skript die Methode 2 von oben verwenden. Die aktuelle Shell muss die interpretierende Shell sein, d. h. das Skript muss als Quelle verwendet werden. Die Shell darf nichts ändern exec
, da Sie nicht möchten, dass die Shell durch irgendetwas ersetzt wird. Beispiel für eine Lösung:
#!/usr/bin/false
[ -n "$SSH_AUTH_SOCK" ] || eval "$(ssh-agent)"
ssh-add /media/MyUSB/.ssh/id_00123
Es gibt noch mehr Verbesserungen:
#!/usr/bin/false
da der Shebang sicherstellt, dass das Skript nichts tut und fehlschlägt, wenn Sie es (versehentlich) ausführen, anstatt es zu beziehen. Weitere Strategien finden Sie hier:Strategie für das Vergessen, ein Skript auszuführen mitsource
.Ohne viel Aufhebensoder mit einem Shebang, der aufbash
odersh
eine andere kompatible Shell zeigt, würde das ausgeführte Skript (ohne Quelle) einen neuen Agenten starten, ihm den Schlüssel hinzufügen und beendet werden. All dies ohne Auswirkungen auf die Umgebung Ihrer aktuellen Shell, sodass der Agent dort vergeblich und fast unzugänglich herumsitzen würde. Sie müssten einige Mühe aufwenden, um ihn zu finden und zu beenden, oder einige Mühe aufwenden, um seinen Socket zu finden und ihn manuellSSH_AUTH_SOCK
in der Umgebung Ihrer Shell einzustellen; oder Sie würden ihn einfach so lassen,false
da der Shebang dieses unbequeme Szenario verhindert.[ -n "$SSH_AUTH_SOCK" ]
prüft, ob$SSH_AUTH_SOCK
es sich zu einer nicht leeren Zeichenfolge erweitert. Eine leere Zeichenfolge gibt an, dass kein Agent verfügbar ist, während eine nicht leere Zeichenfolge angibt, dass wahrscheinlich ein Agent vorhanden ist. Das Skript startetssh-agent
nur dann neu, wenn die Zeichenfolge leer ist. Dies ist eine grundlegende Vorsichtsmaßnahme für den Fall, dass Sie das Skript (versehentlich) zum zweiten Mal aufrufen, einen neuen Authentifizierungsagenten erstellen und Variablen verlieren, die mit dem vorherigen Agenten verknüpft sind und der nutzlos weiterläuft.Das ist nicht nötig
sleep
.ssh-agent
Unser Skript wird beendet, wenn der Agent (also sein untergeordnetes Element im Hintergrund) bereit ist. Sie können esssh-add
sofort tun.ssh-add
ist hier als synchroner Befehl vorhanden. Wenn Sie es asynchron ausführen (mit&
, wie Sie es versucht haben), sparen Sie wahrscheinlich nicht viel Zeit. Sie können es versuchen. Aber Sie werden das Skript höchstwahrscheinlich aus einer interaktiven Shell mit aktivierter Jobsteuerung beziehen und daher&
(wenn Sie es dort einfügen) Ihr Terminal mit einer Meldung wie verunreinigen[1]+ Done …
.