Über Expect.pm Dateien auf Linux-Remote-Host schreiben
Stefan Welscher
- perl
Hi,
nachdem ich das lesen von Dateien über Perl-Expect inzwischen hinbekommen habe stecke ich beim nächsten Thema fest:
Dateien auf einem Remote-Linux-Server schreiben.
Ich habe weiterhin nur die SSH-Session, die ich über Expect aufgebaut habe, zur Verfügung, kein SCP, etc..
Trotzdem bräuchte ich jetzt eine Möglichkeit auf der entfernten Maschine Dateien anzulegen. Aber wie könnte ich das machen?
Mit echo "text" > file.txt komm ich nicht weiter, wenn der Text Anführundszeichen enthält, ohne die Anführungszeichen wird schon beim nächsten Leerzeichen geschnitten und wie ich z.B. VI mit Expect kontrollieren soll kann ich mir auch gerade nicht vorstellen. Am optimalsten wäre ein Befehl bei dem ich, wie bei Cisco-Bannern, eine Zeichenfolge festlegen kann bis zu der ein Text eingelesen wird.
Allerdings wüsste ich keinen Befehl unter Linux, mit dem ich das anstellen könnte.
Habt ihr irgendwelche Tipps?
Moin Moin!
Ganz ohne Perl auf jedem halbwegs brauchbaren Unix-System:
ssh user@remotehost cat '>' /some/where/a/file.ext < /home/ich/lade/mich/hoch.foo
(oder, je nach SSH und OS auf dem Remotehost:)
ssh user@remotehost sh -c 'cat > /some/where/a/file.ext' < /home/ich/lade/mich/hoch.foo
Der Trick ist, die Ausgabe-Umleitung von cat zu maskieren, so dass diese Umleitung auf dem Remote-Host stattfindet. cat bekommt als STDIN hoch.foo durch den ssh-Kanal, STDOUT wird auf dem Remotehost in die Zieldatei umgeleitet.
Der Trick funktioniert auch für mehrere Dateien, wenn man einen Packer zu Hilfe nimmt:
tar czf - -C /local/upload/directory | ssh user@remotehost tar xzvf - -C /remote/directory
Das erste tar wechselt ins Verzeichnis /local/upload/directory, packt alle Inhalte in einen TAR-Datenstrom und komprimiert den Datenstrom mit gzip (Parameter z), der schließlich nach STDOUT geschrieben wird (Parameter f -). Das geht via ssh als STDIN an das zweite Tar, das ins Verzeichnis /remote/directory wechselt und dort den Datenstrom wieder dekomprimiert und in Dateien und Verzeichnisse schreibt. Mit j (neuere Versionen) bzw. y (ältere Versionen) statt z wird statt gzip bzip2 benutzt, das oft besser komprimiert. Manche tar-Versionen können nicht selbst ein Komprimierungsprogramm aufrufen, dann läßt man z einfach weg oder bastelt gzip lokal und gunzip remote in den Datenstrom.
Natürlich funktionieren auch andere Formate wie z.B. cpio, aber TAR hat den Vorteil, ohne seek() auszukommen, im Gegensatz zu ZIP.
Wenn die Dateien nur geringfügig verändert werden müssen, wäre diff auf der lokalen Seite und patch auf der Remote-Seite ein Ansatz, das erfordert aber etwas Disziplin. Eleganter ist rsync, das auch wunderbar über eine SSH-Verbindung arbeiten kann:
rsync -avzu -e "ssh -l user" /local/upload/directory remotehost:/remote/directory
Damit wird das /remote/directory auf den Stand des /local/upload/directory gebracht, wobei auf beiden Seiten heftig gerechnet wird, aber nur minimale Datenmengen fließen.
Weitere hilfreiche Parameter für rsync:
-x bleibe im selben Dateisystem, gemountete Dateisysteme in Unterverzeichnissen werden ignoriert.
-H Hardlinks erkennen und remote anlegen (langsamer)
--progress Fortschrittanzeige
--copy-unsafe-links "Unsichere" Symlinks kopieren
--numeric-ids User und Group ID numerisch übertragen statt Namen zu vergleichen
rsync läuft (mit etwas Mühe) übrigens auch unter Windows. tar und gzip/gunzip sowieso. Kommandozeilen-SSH-Clients gibt es ebenfalls für Windows, z.B. plink aus dem PuTTY-Paket. Mit cygwin gibt es die gesamte Palette.
Alexander
Danke für die ausführliche Antwort, aber ich gehe über mehrere Hops und hab unterschiedliche Dateien in unterschiedlichen Verzeichnissen.
Das ganze soll auch vom Webinterface aus steuerbar sein, da bin ich imho schon auf Perl angewiesen.
Ich hab mir jetzt einen Workaround geschrieben, der alle Sonderzeichen, die auf der Konsole Probleme machen könnten durch Strings ersetzt und am Ende auf dem Zielhost ein Script zur Rückkonvertierung anstößt. Expect kann dann normal mit "echo $content > $file" arbeiten. Ist keine wirklich schöne Lösung, da sich die zu übertragende Datenmenge bei ersten Versuchen verdreifacht hat, aber es funktioniert und das sogar mit Binärdateien.
Trotzdem Danke!
Moin Moin!
Danke für die ausführliche Antwort, aber ich gehe über mehrere Hops und hab unterschiedliche Dateien in unterschiedlichen Verzeichnissen.
Dann eben Datei für Datei. Mehrere Hops machen übrigens gar nichts, ssh kann man beliebig kaskadieren. Ein ssh-agent hilft dann, sich die Passwort-Eingabe zu schenken.
ssh user1@start ssh user2@hop1 ssh user3@hop2 ssh user4@ziel cat > /ziel/datei < /quelle/datei
Eine andere Technik ist mir neulich begegnet: Man richtet sich eine $HOME/.ssh/config ein, die beim ssh zum ersten Hop ein Portforwarding zum SSH-Port des nächsten Hop einrichtet. Dann reicht ein ssh zieluser@localhost -p 12345, und ssh tunnelt ssh.
Ich gebe zu, beide Techniken sind hervorragend geeignet, einen Knoten im Kopf zu bekommen.
Das ganze soll auch vom Webinterface aus steuerbar sein, da bin ich imho schon auf Perl angewiesen.
Wieso? CGIs müssen nicht in Perl geschrieben sein. Wenn Du mod_perl verwendest, ist das natürlich etwas schwieriger.
Aber selbst in Perl brauchst Du kein Expect:
my $pid=fork();
die "Can't fork: $!" unless defined $pid;
if ($pid) {
# parent
wait($pid);
} else {
# child
close STDIN;
open STDIN,'<',$filename or die "Can't open $filename: $!";
exec '/usr/bin/ssh','user@host','cat','>',$destfilename;
# oder eben kaskadiert:
# exec '/usr/bin/ssh','user1@start','ssh','user2@hop1','ssh','user3@hop2','ssh','user4@ziel','cat','>',$destfilename;
die 'ssh failed: $!";
}
Net::SSH::Perl wäre eine Möglichkeit, komplett ohne fork() auszukommen.
Ich hab mir jetzt einen Workaround geschrieben, der alle Sonderzeichen, die auf der Konsole Probleme machen könnten durch Strings ersetzt und am Ende auf dem Zielhost ein Script zur Rückkonvertierung anstößt. Expect kann dann normal mit "echo $content > $file" arbeiten. Ist keine wirklich schöne Lösung, da sich die zu übertragende Datenmenge bei ersten Versuchen verdreifacht hat, aber es funktioniert und das sogar mit Binärdateien.
Base64-Encoding wäre noch eine Variante, kostet konstant 30% mehr Volumen. Aber ssh braucht das für STDIN/STDOUT/STDERR nicht, das ist 8-bit-clean. sz und rz gäbe es auch noch ... *schauder*
Alexander
Ich gebe zu, beide Techniken sind hervorragend geeignet, einen Knoten im Kopf zu bekommen.
OK, du hast schon recht, dass sich vieles, wenn nicht sogar alles über die Shell erledigen lässt, aber ich komm mit Perl einfach besser zurecht. :)
Base64-Encoding wäre noch eine Variante, kostet konstant 30% mehr Volumen. Aber ssh braucht das für STDIN/STDOUT/STDERR nicht, das ist 8-bit-clean. sz und rz gäbe es auch noch ... *schauder*
Sehr guter Tipp! Ich hab das jetzt so implementiert und es funktioniert so gut wie meine eigene Lösung, nur eben mit der Hälte des Speicherbedarfs. Ärgerlich nur, wenn ich sehe, wie viel Zeit ich mit meiner überflüssigen Lösung verbracht habe :/.