SCP für die zuletzt geänderte Datei in einem Remote-Verzeichnis

386
Neil Philip

Gibt es einen einfachen Befehl zum SCP der zuletzt geänderten Datei in einem Verzeichnis auf einem Remote-Host? Ich kann herausfinden, wie ich das von lokal zu fern mache. Etwas wie: scp ``ls -Art | tail -n 1\`` usr@remote:/var/log/yeet Wie kann ich dasselbe tun, aber von fern nach lokal. (Holen Sie sich also die zuletzt geänderte Datei von yeet und kopieren Sie sie auf den lokalen Host.)

0

1 Antwort auf die Frage

0
Kamil Maciorowski

Hier gibt es einige Probleme.

Parsing ls

Zuallererst solltenls Sie nicht analysieren . Ihr ls -Art | tail -n 1ist fehlerhaft und kann nicht zuverlässig behoben werden. Ein zuverlässiger Standardersatz ist findund / oder das Arbeiten mit nullterminierten Leitungen.

Ich gehe außerdem davon aus, dass Sie die zuletzt geänderte Datei (kein Verzeichnis) direkt im aktuellen Verzeichnis (nicht in einem Unterverzeichnis) benötigen.

Die Möglichkeit, Eingabeelemente in einer Form mit nullterminierter Liste zu parsen, ist für POSIX normalerweise nicht erforderlich. Wenn Ihre Werkzeuge reich an Optionen sind, ist dies ein robuster Ersatz für Ihre ls -Art | tail -n 1:

find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zrn | head -zn 1 | cut -z -d " " -f 2- 

Das Ergebnis wird als nullterminierter Dateiname angezeigt. Um etwas damit zu tun, müssen Sie es weiterleiten xargs -0 …, zB:

… | xargs -0r cp -t "/target/dir/" -- 

oder (falls Ihr cpnicht unterstützt -t):

… | xargs -0r -I {} cp -- {} "/target/dir/" 

In diesen Befehlen gibt es viele Dinge, die POSIX nicht benötigt (also nicht portabel). Dazu gehört auch --, dass cpdas Parsing von Optionen beendet wird. Eine Datei -Rlöst also keine Rekursion aus, anstatt kopiert zu werden. Beachten Sie, dass Dateinamen, die unseren Namen verlassen find . …, mit .Sicherheit beginnen, sodass --sie sicher weggelassen werden können. Ich benutzte es nur, um auf eine gute allgemeine Praxis im Umgang mit hinzuweisen cp. Eine andere bewährte Methode ist das Zitieren von Pfaden: Das Literal /target/dir/würde ohne Anführungszeichen funktionieren, aber nachdem Sie dieses Beispiel durch Ihren spezifischen Zielpfad ersetzt haben, müssen Sie möglicherweise Anführungszeichen verwenden, daher verwende ich sie trotzdem.

In Ihrem (lokalen zu entfernten) Fall würden Sie scpanstelle von verwenden cp. Beim Parsen von Argumenten kann es seine eigenen Macken haben.

Ein POSIX-kompatibler, aber schlecht funktionierender Befehl kann Folgendes sein:

find . ! -name . -prune -type f -exec sh -c ' [ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; | wc -c)" -eq 0 ]' sh {} \; -print 

Es passt einen Ansatz aus dieser anderen Antwort an den Ersatz (Nicht-POSIX) an -maxdepth 1. Es erscheint, shum zwei find … -exec …Klauseln zuverlässig zu verschachteln . Das Äußere findprüft alle Dateien und liefert sie einzeln an das Innere find. Das Innere findfindet alle Dateien, die neuer als die angegebene Datei sind, und druckt afür jede neuere Datei ein einzelnes Zeichen ( ). wc -czählt diese Zeichen. Wenn keine vorhanden ist, bedeutet dies, dass die angegebene Datei die zuletzt geänderte Datei ist. erst dann finddruckt es das Äußere .

Es gibt einige Szenarien, in denen die äußere findDatei möglicherweise mehr als eine Datei druckt:

  • es gibt zwei oder mehr Dateien mit der gleichen "neuesten" mtime;
  • Dateien im Verzeichnis werden geändert, während der Befehl ausgeführt wird.
  • Das Innere findkann einige Dateien nicht statisieren.

Aus diesem Grund wäre die Endaktion -quitdes Äußeren findnützlich (beachten Sie, dass dies auch für das Innere nützlich wäre find, jedoch aus einem anderen Grund). Ist leider -quitnicht POSIX.

Ich habe verwendet -print( -print0ist nicht POSIX), dennoch sind nicht standardmäßige Dateinamen kein Problem, da Sie die Ausgabe nicht an einen anderen Befehl weiterleiten müssen. Verwenden Sie einfach das -exec, was alle möglichen Dateinamen richtig behandelt; zB anstelle von -printdir verwenden:

-exec yet_another_command {} \; 

Jetzt wissen Sie, wie Sie die zuletzt geänderte Datei in einem lokalen Verzeichnis finden, ohne sie zu analysieren ls.

Dateien auf einem Remote-System suchen

Welchen Ansatz Sie auch wählen (einschließlich der fehlerhaften ls … | tail …), Sie müssen den Befehl auf dem Remote-System ausführen (oder nicht, ich komme später zu dieser Ausnahme), um die gewünschte Datei in einem Remote-Verzeichnis zu finden.

Der naheliegendste Ansatz ist sshder Einstieg in das Remote-System. In dem neuen Kontext ist das Remote-System lokal und Ihr lokaler Computer ist Remote. (Hier verwende ich den obigen POSIX-kompatiblen Befehl als Beispiel, aber Sie können unseren ersten verwenden, find … | sort -z … | head -z … | cut -z … | xargs -0 …wenn nur das Remote-System alle erforderlichen Optionen unterstützt.)

ssh usr@remote # now on the remote system cd "/source/dir/" && find . ! -name . -prune -type f -exec sh -c ' [ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; | wc -c)" -eq 0 ]' sh {} \; -exec scp {} usr@local:"/target/dir/" \; 

Beachten Sie, wenn Sie es vermeiden cdund verwenden möchten, dann müssen find /source/dir …mehrere .-s durch ersetzt werden /source/dir. Mit geht es einfacher cd.

Ihr lokales System muss über SSH vom Remote-System aus erreichbar sein. Wenn NAT unterwegs ist, können Sie es mit einer Remote-Portweiterleitung umgehen, etwa wie folgt:

ssh -R 12322:127.0.0.1:22 usr@remote # now on the remote system the same cd + find as above # only the scp part is different  … -exec scp -P 12322 {} usr@127.0.0.1:"/target/dir/" \; 

Beachten Sie, dass der Remote-Port 12322zu Ihrem lokalen Anschluss führt, sshdund jeder Benutzer des Remote-Systems kann versuchen, ihn zu missbrauchen. Ein weiteres Problem: Das Remote-System ist möglicherweise so konfiguriert, dass Sie überhaupt keinen Port weiterleiten können.

Möglicherweise möchten Sie, dass ein einzelner Befehl auf dem lokalen System aufgerufen wird. In diesem Fall ist ein korrektes Zitat erforderlich und es wird noch umständlicher:

ssh usr@remote ' cd "/source/dir/" && find . ! -name . -prune -type f -exec sh -c '"'"' [ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; | wc -c)" -eq 0 ]'"'"' sh {} \; -exec scp {} usr@local:"/target/dir/" \; ' 

Ich erwarte Probleme, wenn die Fernbedienung jedoch scpnach Ihrem Passwort fragen muss. Aus diesem Grund oder wenn Sie Ihr lokales Ziel nicht erreichen sshdkönnen, benötigen Sie möglicherweise einen anderen Ansatz.

Dieser lokale Befehl gibt den gewünschten Dateinamen aus dem Remote-System aus:

ssh usr@remote ' cd "/source/dir/" && find . ! -name . -prune -type f -exec sh -c '"'"' [ "$(find . ! -name . -prune -type f -newer "$1" -exec printf a \; | wc -c)" -eq 0 ]'"'"' sh {} \; -print ' 

Sie können es mit lokalen Tools wie xargsund verwenden scp. Beachten Sie, dass die Analyse der -printRenditen nur geringfügig besser ist als die Analyse ls. Verwenden Sie -print0(non-POSIX, ist möglicherweise nicht verfügbar) oder -exec printf "%s\0" {} \;(sollte funktionieren), anstatt -printden gewünschten Dateinamen als nullterminierte Zeichenfolge zu erhalten. Nun liegt es an Ihnen, was Sie auf lokaler Seite damit tun. Dies ist nützlich, wenn Sie einen POSIX-kompatiblen Fernbefehl benötigen, die Tools in Ihrem lokalen System jedoch reich an Optionen sind.

Bemerkenswerte Ausnahme: sshfs

sshfserlaubt es Ihnen usr@remote:"/source/dir/"als /local/path/. Siehe meine Antwort, ich werde mich nicht wiederholen, um alle Details zu behandeln. In Ihrem Fall sieht die (lokale) Prozedur mit reichen (nicht auf POSIX beschränkten) Werkzeugen wie folgt aus:

sshfs usr@remote:"/source/dir/" "/local/path/" find "/local/path/" -maxdepth 1 -type f -printf '%T@ %p\0' | sort -zrn | head -zn 1 | cut -z -d " " -f 2- | xargs -0r cp -t "/target/dir/" -- fusermount -u "/local/path/" 

Das ist toll. Alles was Sie mit lokalen Werkzeugen machen. Wenn Sie nur sshfsdie Tools auf der Remote-Seite verwenden können, spielen die verfügbaren Optionen keine Rolle mehr. Es ist auch egal, ob Sie Ihr lokales System von außen erreichen können. Und die Methode ist die gleiche: Remote zu Local oder Local zu Remote ist egal.