Python: File Transfer Protocol (FTP)
► Python-Dokumentation: ftplib
Mit Funktionen aus dem Python-Modul ftplib ist es möglich, Daten mit einem entfernten Server über das File Transfer Protocol auszutauschen. Zur Veranschaulichung werden hier einige elementare Funktionen beschrieben. Das Modul ist auch in der Lage, verschlüsselten Datenaustausch mit TLS zu ermöglichen, was hier aber nicht beschrieben wird. Es ist also zu beachten, dass die hier beschriebenen Vorgänge nicht verschlüsselt sind, weshalb der Datenaustausch so prinzipiell abgelauscht werden kann.
Verbindung mit dem FTP-Server aufbauen
► Python-Dokumentation: FTP() getwelcome() cwd() dir()
Für den Datenaustausch zwischen lokalem Rechner und entferntem Server oder für andere Datei-Aktionen muss zunächst mit FTP() ein neues FTP-Objekt erzeugt werden, das in diesem Beispiel der Variablen server zugewiesen wird, über die alle folgenden Aktionen dann ausgeführt werden können. Als Argumente werden der gewünschte Host, der Benutzername und das dazugehörige Passwort benötigt.
Mit encoding() wird die Zeichenkodierung festgelegt (Vorgabewert ist in Python 3.8 Latin-1, was bei Umlauten usw. zu Problemen führen kann).
Mit getwelcome() lassen sich serverspezifische Informationen anzeigen.
Mit cwd() (current working directory) kann bei Bedarf in ein anderes Verzeichnis auf dem Server gewechselt werden.
Die Methode dir() gibt eine Liste der gefundenen Dateien mit ihren Eigenschaften aus.
# je nach Aktion werden folgende Module benötigt:
import ftplib, os, time, calendar, io
with ftplib.FTP(host="example.org", user="mausi123", passwd="geheim007") as server:
server.encoding="utf-8"
print(server.getwelcome())
server.cwd("httpdocs")
server.dir()
FTP-Befehle
► Python-Dokumentation: sendcmd()
Das Modul ftplib stellt eine Reihe von Wrappern für FTP-Befehle zur Verfügung, die auch direkt mit der Methode sendcmd() (send command) an den Server geschickt werden können. Allerdings stehen nicht immer alle FTP-Befehle auf dem jeweiligen Server zur Verfügung. Der Befehl FEAT (features) gibt die verfügbaren FTP-Befehle zurück:
print(server.sendcmd("FEAT"))
Ordner auslesen
► Python-Dokumentation: mlsd()
Die oben erwähnte Methode dir() gibt eine Übersicht der Objekte eines Verzeichnisses aus, die nur umständlich maschinenlesbar ist. Einfacher ist es daher, mit mlsd() (make list of directory) die benötigten Objekt-Eigenschaften in ein Tupel einzulesen, das anschließend verarbeitet werden kann. Alle benötigten Eigenschaften (sog. facts) müssen in der Liste des zweiten Arguments von mlsd() aufgeführt werden. Das erste Argument (hier: remotedir) enthält den Pfad zu dem gewünschten Verzeichnis.
Der Rückgabewert ist ein Generator-Objekt, mit einem Eintrag pro gefundenem Objekt (hier: row), das als erstes Element den Namen des Objekts und als zweites Element ein Dictionary mit den Werten der facts der gefundenen Objekte enthält, die über das entsprechende Schlüsselwort abgefragt werden können.
for row in server.mlsd(remotedir, ["type", "modify", "perm", "size"]):
if row[1]["type"] == "file":
print(row[0], row[1]["type"], row[1]["modify"], row[1]["perm"], row[1]["size"])
else:
print(row[0], row[1]["type"], row[1]["modify"], row[1]["perm"])
Download
► Python-Dokumentation: retrbinary()
Folgendes Beispiel demonstriert den Download der Datei remotefile vom Server an den lokalen Ort localfile mit der Methode retrbinary(). Dabei wird die Datei localfile zum Schreiben geöffnet ("wb" - write binary) und mit den Daten aus remotefile gefüllt. Existiert remotefile nicht, wird localfile daher dennoch angelegt, allerdings mit 0 Byte Größe und es gibt eine Fehlermeldung.
Beim Download wird der Zeitstempel mtime für localfile auf den Zeitpunkt des Downloads gesetzt, was unter Umständen nicht erwünscht ist, da der Zeitpunkt der wirklich letzten Änderung der Datei auf dem Server für die Datei remotefile gespeichert ist, und man diesen Zeitpunkt gerne bewahren möchte. Daher muss man in diesem Fall diesen Zeitpunkt von remotefile speichern (hier in der Variable remote_mtime) und ihn dann mit os.utime() der Datei localfile wieder zuweisen. Dabei ist zu beachten, dass der Zeitstempel, der zuvor mit mlsd() ermittelt wurde, nicht in gewöhnlicher Unixzeit, sondern im Format YYYYMMDDhhmmss ausgegeben wurde, und vor der Änderung des Zeitstempels von localfile zunächst mit calendar.timegm() umgewandelt werden muss.
with open(localfile, "wb") as f:
server.retrbinary(f"RETR {remotefile}", f.write)
mtime = calendar.timegm((int(remote_mtime[0:4]), int(remote_mtime[4:6]), int(remote_mtime[6:8]), int(remote_mtime[8:10]), int(remote_mtime[10:12]), int(remote_mtime[12:14]), 0, 0, 0)) # unix time
os.utime(localfile, (mtime, mtime))
► Python-Dokumentation:retrlines()
Um den Inhalt einer serverseitigen Textdatei lediglich einzulesen, ohne sie lokal zu speichern, kann die Methode retrlines() verwendet werden, wie folgendes Beispiel veranschaulicht:
lines = []
server.retrlines(f"RETR {remotefile}", lines.append)
filecontent = "\n".join(lines)
print(filecontent)
Upload
► Python-Dokumentation: storbinary()
Folgendes Beispiel demonstriert den Upload der lokalen Datei localfile an den Ort remotefile auf dem Server mit der Methode storbinary(). Dabei wird die Datei localfile zum Lesen geöffnet ("rb" - read binary) und mit diesen Daten die Datei remotefile gefüllt. Existiert localfile nicht, kann remotefile auch nicht angelegt werden und es gibt eine Fehlermeldung.
Beim Upload wird der Zeitstempel mtime für remotefile auf den Zeitpunkt des Uploads gesetzt, was unter Umständen nicht erwünscht ist, da der Zeitpunkt der wirklich letzten Änderung der Datei lokal für die Datei localfile gespeichert ist, und man diesen Zeitpunkt gerne bewahren möchte. Daher muss man in diesem Fall diesen Zeitpunkt von localfile speichern (hier in der Variable local_mtime) und ihn dann mit dem FTP-Befehl MFMT (modify file modification time) der Datei remotefile wieder zuweisen. Dabei ist zu beachten, dass der Wert vom mtime hier nicht im Format der gewöhnlichen Unixzeit angegeben wird, sondern im Format YYYYMMDDhhmmss, das erst mit strftime() erzeugt werden muss.
with open(localfile, "rb") as f:
server.storbinary(f"STOR {remotefile}", f)
local_mtime = os.stat(localfile).st_mtime # unix time
mtime = time.strftime("%Y%m%d%H%M%S", time.gmtime(local_mtime)) # YYYYMMDDhhmmss
server.sendcmd(f"MFMT {mtime} {remotefile}")
Es ist aber auch möglich, ein Dateiobjekt aus einem String zu erzeugen und dieses ohne lokales Zwischenspeichern auf dem Server zu speichern:
filecontent = "Hund\nKatze\nMaus"
f = io.BytesIO(bytes(filecontent, "utf-8"))
server.storbinary(f"STOR {remotefile}", f)
Datei erzeugen und löschen
► Python-Dokumentation: delete()
Es gibt zwar keinen Befehl, um auf dem Server eine leere Datei direkt zu erzeugen, aber es ist möglich, wie oben beschrieben, ein Dateiobjekt aus einem leeren String zu erzeugen und dieses als Datei zu spreichern.
Um eine Datei auf dem Server zu löschen, wird die Methode delete() verwendet. Als Argument wird der Dateiname übergeben.
# eine leere Datei erzeugen:
server.storbinary(f"STOR {remotefile}", io.BytesIO(b""))
# eine Datei löschen:
server.delete(remotefile)
Verzeichnis erzeugen und löschen
► Python-Dokumentation: mkd() rmd()
Mit der Methode mkd() (make directory) kann ein neues Verzeichnis auf dem Server angelegt werden.
Um ein Verzeichnis auf dem Server zu löschen, wird die Methode rmd() (remove directory) verwendet. Dabei darf dieses Verzeichnis keine Objekte enthalten, muss also vor dem Löschen geleert werden, ansonsten kommt es zu einer Fehlermeldung.
# ein Verzeichnis erzeugen
server.mkd(remotedir)
# ein leeres Verzeichnis löschen
server.rmd(remotedir)
Objekt umbenennen
► Python-Dokumentation: rename()
Um ein Verzeichnis oder eine Datei auf dem Server umzubennen, wird die Methode rename() verwendet. Als Argumente werden der alte und der neue Name übergeben.
server.rename(oldname, newname)