Übergabe mehrerer Dateien über stdin (über ssh)


15

Ich habe ein Programm auf einem Remote-Host, dessen Ausführung ich automatisieren muss. Der Befehl, mit dem dieses Programm auf demselben Computer ausgeführt wird, sieht ungefähr so ​​aus:

/path/to/program -a file1.txt -b file2.txt

In diesem Fall werden file1.txtund file2.txtfür ganz andere Dinge innerhalb des Programms verwendet, sodass ich sie nicht einfach catzusammenstellen kann. Aber in meinem Fall, das file1.txtund file2.txtdas möchte ich in das Programm zu übergeben existiert nur auf dem Gerät, nicht auf dem Host , wo ich das Programm auszuführen. Ich weiß, dass ich mindestens eine Datei durch SSH füttern kann, indem ich sie durchgebe stdin:

cat file1.txt | ssh host.name /path/to/program -a /dev/stdin -b file2.txt

Da ich aber keine Dateien auf dem Host speichern darf, brauche ich auch eine Möglichkeit, um die Dateien dorthin zu bringen file2.txt. Ich denke, es könnte durch den Missbrauch von Umgebungsvariablen und den kreativen Einsatz von catund sedzusammen möglich sein, aber ich kenne die Tools nicht gut genug, um zu verstehen, wie ich sie verwenden würde, um dies zu erreichen. Ist es machbar und wie?


2
catund sedsind hier nicht die lösung.
Slyx

Wahrscheinlich könnte ich mounten, aber angesichts der Proxys und Sicherheitsbeschränkungen bin ich mir nicht sicher, ob ich damit durchkommen würde.
Green Cloak Guy

Haben Sie auf dem Remotecomputer die Berechtigung, einen SSH-Ordner bereitzustellen?
Slyx

1
Wenn Sie eine SSH-Sitzung vom Remotecomputer auf Ihrem lokalen Computer öffnen können, ist es auf Netzwerkebene kein Problem, einen SSH-Ordner bereitzustellen.
Slyx

Können Sie Weiterleitungen durchführen? Welche Systeme und Shells haben Sie am lokalen und am entfernten Ende?
Mosvy

Antworten:


18

Wenn es sich bei den als Argumente für Ihr Programm angegebenen Dateien um Textdateien handelt und Sie deren Inhalt steuern können (Sie kennen eine Zeile, die in ihnen nicht vorkommt), können Sie mehrere Here-Dokumente verwenden:

{
    echo "cat /dev/fd/3 3<<'EOT' /dev/fd/4 4<<'EOT' /dev/fd/5 5<<'EOT'"
    cat file1
    echo EOT
    cat file2
    echo EOT
    cat file3
    echo EOT
} | ssh user@host sh

Hier catist ein Beispielbefehl, der Dateinamen als Argumente verwendet. Es könnte stattdessen sein:

echo "/path/to/prog -a /dev/fd/3 3<<'EOT' -b /dev/fd/4 4<<'EOT'

Ersetzen Sie jeweils EOTdurch etwas, das nicht in jeder der Dateien vorkommt.


1
Dieser Ansatz ist ziemlich clever! Wenn die Dateien kein Text sind, können Sie sie mit etwas wie base64 oder sogar einem guten alten Uuencode codieren / decodieren ... Sehr schön!
Filbranden

3
Beachten Sie, dass mehrere (die meisten) sh-Implementierungen hier Dokumente mithilfe temporärer Dateien implementieren. Daher funktioniert dies möglicherweise nicht für das OP, wenn sie keine Dateien auf dem Remote-Host speichern dürfen.
Stéphane Chazelas

2
dash(das /bin/shin debian, ubuntu, etc), busybox shdas /bin/shvon FreeBSD und yashverwende keine temporären Dateien für here-Dokumente. Ich habe es tatsächlich mit einer schreibgeschützten Chroot getestet. Natürlich müssen die /dev/fd/Dateien verfügbar sein - auf einem FreeBSD-System nur /dev/fd/0-2, fdescfsmuss also gemountet werden /dev/fd.
Mosvy

1
Ich bin ein Fan der ASCII-Informationstrennzeichen , mit denen Felder ohne Hindernisse abgegrenzt werden können. U+001Cist "Information Separator Four" (Dateitrennzeichen, FS) und ist ideal für diesen Fall, obwohl Binärdateien es zufällig verwenden können. Daher würde ich aus Kompatibilitätsgründen EOT="$(printf $'\x1c')"in fortgeschrittenen Shells oder sonst vorschlagen EOT="$(awk 'BEGIN{printf"%c",28}')". Sie müssen es zitieren, wenn Sie es einsetzen, z. B. echo "$EOT$EOT$EOT"(verdreifacht, um die Wahrscheinlichkeit des Abgleichs einer Binärdatei zu verringern).
Adam Katz

Wenn Sie das tty benutzen wollen, warum nicht den ttyBefehl benutzen ? Es sei denn, ich vermisse etwas - was ich denke, ist möglich?
Pryftan,

14

Vielleicht nicht genau das, was Sie wollen ... Aber vielleicht überlegen Sie, einen Tarball durch die von ssh geöffnete Pipe zu schicken?

Du hast das gesagt:

Ich darf keine Dateien auf dem Host speichern.

Es ist möglich, dass Sie kein beschreibbares Basisverzeichnis oder einen anderen geeigneten Speicherort für Dateien haben, aber ich würde sagen, dass es unwahrscheinlich ist, dass Sie keinen beschreibbaren Speicherort haben, auch wenn ein temporärer Speicherort tmpfsnur für Sie verfügbar ist bestimmte Verbindung.

Viele Programme (und sogar libc-Routinen) erfordern eine beschreibbare Datei /tmp, daher ist es sehr wahrscheinlich, dass Ihnen eine zur Verfügung steht.

Dann könnten Sie ein Skript verwenden, das den Tarball in ein temporäres Verzeichnis entpackt, Ihr Programm ausführt und über die ssh-Verbindung aufräumt.

Etwas wie:

$ tar cf - file1.txt file2.txt |
  ssh host.name '
      set -e
      tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX)
      cleanup () { rm -rf "$tmpdir"; }
      trap cleanup EXIT
      cd "$tmpdir"
      tar xf -
      /path/to/program -a file1.txt -b file2.txt
  '

Dies erfordert möglicherweise zusätzliche Sorgfalt bei Dateipfaden und es sind einige Eckfälle zu berücksichtigen (auf sie testen), aber der allgemeine Ansatz sollte funktionieren.

Wenn kein beschreibbares Verzeichnis verfügbar ist, besteht ein möglicher Ansatz programdarin, einen Tarball als einzelne Eingabe zu verwenden und seinen Inhalt in den Speicher zu entpacken. Wenn es sich beispielsweise programum ein Python-Skript handelt, kann mit dem integrierten tarfileModul auf einfache Weise so etwas erreicht werden.


3
Ich habe mich mehr oder weniger dazu entschlossen, einfach eine Datei zu schreiben /tmp/und sie direkt zu verwenden, ohne die Mühe zu haben, sie zu tarballen .
Green Cloak Guy

2
Der Tarball-Ansatz hat einige Vorteile, da Sie eine einzelne SSH-Verbindung verwenden (Sie müssen nicht auf Erfolg / Fehler testen) und es unwahrscheinlich ist, dass mehrere gleichzeitige Läufe ineinander laufen (verwenden und / oder löschen Sie Dateien in / tmp, die für separate Läufe bestimmt sind) .) Aber ich denke, das ist das Beste, was Sie von ssh bekommen, wie es jetzt existiert. Viel Glück!
Filbranden

3
@GreenCloakGuy Also nach allem, Sie können Dateien speichern , auf dem Server nur in Ordnung. Bitte ändern Sie die Frage entsprechend.
Mosvy

1
@mosvy kann ja, will aber nicht . Die Frage steht, in einem "ist es möglich, dies zu tun, ohne irgendwelche Dateien zu speichern", auf die die Antwort zu sein scheint "vielleicht, aber nicht in meinem Fall"
Green Cloak Guy

5

Wenn Sie einen TCP-Listener festlegen können (kann ein höherer Port sein), können Sie eine zweite SSH-Sitzung verwenden, um die zweite Eingabequelle mit herzustellen nc.

Beispiel:

Es gibt dieses Skript auf dem Server ( ~/script.bash):

#!/usr/bin/env bash
cat "$1" | tr a 1
nc localhost "$2" | tr '[:lower:]' '[:upper:]'

Und es gibt diese beiden Dateien lokal:

$ cat a 
aaa
aaa
aaa
$ cat b
bbb
bbb
bbb

Nun starte zuerst die zweite Quelle ( $servist der Server):

ssh "$serv" nc -l -p 2222 -q1 <b &

Führen Sie den richtigen Befehl aus:

$ ssh "$serv" ./script.bash - 2222 <a
111
111
111
BBB
BBB
BBB
[1]+  Done                    ssh "$serv" nc -l -p 2222 -q1 < b

2

Es wurde in einem Kommentar festgelegt , der /tmpbeschreibbar ist. Kopieren Sie daher einfach vorher eine der Dateien:

scp -p file2.txt host.name:/tmp/
ssh host.name "/path/to/program -a /dev/stdin -b /tmp/file2.txt && rm /tmp/file2.txt" < file1.txt

Dadurch wird auch die kopierte Datei nach einer erfolgreichen Ausführung bereinigt (ändern Sie die Option &&in a, ;wenn Sie sie unabhängig vom Erfolg entfernen möchten, beachten Sie jedoch, dass Sie den Exit-Wert verlieren).


Wenn das nicht akzeptabel ist, würde ich vorschlagen, daran zu basteln /path/to/programoder einen Wrapper dafür zu verwenden, mit dem die beiden Dateien von einem einzelnen Eingabestream getrennt werden können, z.

awk 'FNR == 1 && NR > 1 { printf "%c%c%c", 28, 28, 28 } 1' file1.txt file2.txt \
  | ssh host.name /path/to/tweaked_program

Hierbei wird das ASCII-Informationstrennzeichen 4 (File Separator, FS) verwendet und verdreifacht, sodass die Wahrscheinlichkeit minimiert wird, dass eine Binärdatei diese Zeichenfolge zufällig enthält. Sie tweaked_programwürden dann die Eingabe anhand des Trennzeichens aufteilen und dann die beiden gespeicherten Dateien als Variablen bearbeiten.

Wenn Sie eine Sprache mit Bibliotheken für den Umgang mit Tarballs verwenden, ist es natürlich sicherer und sauberer, tarsolchen Code sshwie folgt zu verwenden :

tar -zpc file1.txt file2.txt |ssh host.name /path/to/tweaked_program

Und Sie tweaked_programwürden das Archiv dekomprimieren und öffnen, jede Datei in einer anderen Variablen speichern und dann die programLogik des Originals für die Variablen ausführen .


1

Wenn Sie Ports weiterleiten dürfen sshund Zugriff wgetauf den Remotecomputer und busyboxden lokalen Computer haben, können Sie Folgendes tun:

mkdir /tmp/test; cd /tmp/test
echo 1st_file > 1st_file
echo 2nd_file > 2nd_file

busybox httpd -f -p 127.0.0.1:11080 &
ssh USER@HOST -R 10080:127.0.0.1:11080 '
        cat <(wget -q -O- http://localhost:10080/1st_file) \
            <(wget -q -O- http://localhost:10080/2nd_file)
'
kill $!

(Verwenden Sie catals Beispielprogramm zwei Dateiargumente).

Nur die Möglichkeit, Ports über weiterzuleiten, -Rist von entscheidender Bedeutung. Statt http zu verwenden, können Sie auch andere Methoden verwenden, z. Wenn Ihr netcatdie -dund -NOptionen unterstützt:

nc -Nl localhost 11001 < 1st_file &
nc -Nl localhost 11002 < 2nd_file &
ssh USER@HOST -R 10001:localhost:11001 -R 10002:localhost:11002 '
        cat <(nc -d localhost 10001) <(nc -d localhost 10002)

Möglicherweise gibt es Möglichkeiten, die <(...)Prozessersetzungen zu ersetzen , wenn die Anmeldeshell auf dem Remotecomputer nicht ksh- oder bash-artig ist.

Alles in allem ist dies nicht allzu großartig - eine bessere Kenntnis der genauen System- / Shells- / Konfigurations- / Berechtigungen (die Sie nicht angegeben haben) könnte intelligentere Lösungen ermöglichen.


-1

UPDATE: Das funktioniert nicht wirklich, ssh hat eine sehr genaue Vorstellung davon, was stdin und stdout / stderr bedeuten, und erlaubt es nicht wirklich, stderr zu usurpieren, um daraus zu lesen. Ich werde diese Antwort in ein paar Tagen löschen, da es nicht funktioniert. Danke für das sehr interessante Gespräch !!!


Leider gibt es keine gute Möglichkeit , dies direkt zu tun, da der ssheinzige Client werden die drei Dateideskriptoren (geben stdin, stdoutund stderr) an den Server und haben keine Bestimmungen zusätzliche Dateideskriptoren passieren (die für diesen speziellen Anwendungsfall wäre hilfreich .)

(Beachten Sie auch, dass das SSH-Protokoll Vorkehrungen zur Übergabe zusätzlicher Dateideskriptoren enthält. Nur der sshClient implementiert keine Möglichkeit, diese Funktion zu verwenden. Theoretisch würde das Erweitern des Clients mit einem Patch ausreichen, um diese Funktion verfügbar zu machen.)

Ein schwieriger Weg, um das zu erreichen, wonach Sie suchen, ist die Verwendung von Dateideskriptor 2 (dem stderrDateideskriptor), um die zweite Datei zu übergeben. Etwas wie:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/stderr \
      <file1.txt 2<file2.txt

Dies sollte funktionieren, es könnte nur ein Problem verursachen, wenn programversucht wird, in stderr zu schreiben. Sie können das umgehen, indem Sie die Dateideskriptoren auf der Remote-Seite neu jonglieren, bevor Sie das Programm ausführen. Sie können file2.txtzu Dateideskriptor 3 wechseln und stderr erneut öffnen, um stdout zu spiegeln:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt

Ich habe das nicht wirklich getestet (auf einem Telefon tippen), aber zumindest theoretisch würde ich davon ausgehen, dass es funktioniert. Lassen Sie es mich wissen, falls dies aus irgendeinem Grund nicht der Fall ist. Prost!
Filbranden

Das wird überhaupt nicht funktionieren. Nicht einmal mit einem gehackten SSH. AFAIK stderr verwendet keinen separaten Kanal, sondern eine spezielle Art von Nachricht. Aber ja, es wäre großartig, auf ssh-Kanäle als Pipes oder unbenannte Unix-Domain-Sockets zugreifen zu können (anstatt Socket-Weiterleitungen durchführen zu müssen), es scheint einfach nicht in irgendeiner Art oder Form möglich zu sein.
Mosvy

@mosvy, fd 0, 1 und 2 auf dem Remote-Befehl werden 3 verschiedene Pipes sein. Die Daten werden über 3 verschiedene Kanäle übertragen, die in der ssh-Verbindung gemultiplext sind. Diese sind jedoch unidirektional (von Client zu Server für fd 0 und von Server zu Client für 1 und 2). nicht funktionieren. Unter Linux gibt openening / dev / stderr im schreibgeschützten Modus am anderen Ende der Pipe aus, sodass es auch nie funktionieren würde.
Stéphane Chazelas

Beachten Sie, dass die letzte davon ausgeht, dass die Anmeldeshell des Benutzers auf remote.name Bourne-ähnlich ist ( 3<&2ein Bourne-Shell-Operator, und sshd führt die Anmeldeshell des Benutzers aus, um den vom Client gesendeten Befehl zu interpretieren)
Stéphane Chazelas

2
@ StéphaneChazelas Nö, ein einzelner SSH-Kanal wird für stdin / stdout / stderr verwendet, und die stderr-Daten werden über SSH_MSG_CHANNEL_EXTENDED_DATANachrichten mit dem Typ "" übertragen SSH_EXTENDED_DATA_STDERR. Sie können die ganze Sache hier
mosvy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.