Hej Leute,

wie der Titel schon sagt, geht es in diesem Artikel darum, die R-Befehle kennenzulernen, mit denen man Dateien in einem Verzeichnis auflistet, Dateien umbenennt, kopiert oder löscht. Und das alles in R mit einfachen Funktionen. Das ist super praktisch für viele Automatisierungs-Aufgaben.

Ein Szenario besteht darin, alle Excel- oder pdf-Dateien eines Verzeichnisses in R einzulesen. Oder man will die neueste Datei. die einem gewissen Namensaufbau hat, z.B. „Quartalsreporting 20XX.xlsx“ in einem Verzeichnis zu finden. Ein weiteres Beispiel aus meiner Arbeitspraxis ist das automatische Erstellen eines Backups, bevor ich eine Datei mit neuen Daten aktualisiere.

Natürlich geht das alles auch über Skripte (bash, Powershell) und für komplexere Aufgaben ist das vermutlich auch die bessere Wahl. Der Vorteil, in R zu bleiben, ist aber groß: Der Code ist an einem Ort und für jeden R-Programmierer gut lesbar.

 

Das Arbeitsverzeichnis in R wechseln

Die am meisten verwendeten Befehle sind wohl getwd() und setwd(Verzeichnis). getwd steht für get working directory, der Befehl gibt also das aktuelle Arbeitsverzeichnis zurück. Mit setwd kann man es dementsprechend verändern. R benutzt immer ein Verzeichnis als Arbeitsverzeichnis.

Der Backslash

Für Windows-Nutzer: R benutzt, wie viele andere Programmiersprachen auch, den Backslash \ als sogenannte Escape-Sequenz, d.h. als Startzeichen für ein Sonderzeichen, z.B. eine neue Zeile wird in einem String mit \n dargestellt. Um einen einfachen Backslash in einem String darzugestellen, muss man zwei Backslashes schreiben, also \\. Für Verzeichnisse geht aber auch der Forward-Slash /. Man muss also “C:/temp” oder “C:\temp” benutzen.

Achtung: Fehleranfälligkeit bei absoluten Pfaden

Hier ein Wort der Warnung zu absoluten Pfaden, die als schlechter Stil angesehen werden. Insbesondere, wenn dann auch noch mitten im Code ein setwd verwendet wird. Warum ist das so? Es geht dabei um Wartbarkeit und Portabilität. Solange ein R-Skript nur auf meinem PC vom gleichen Verzeichnis gestartet wird, habe ich kein Problem. Wenn ich es aber verschiebe oder auf einem anderen Rechner starte (Stichwort: produktiv setze), funktionieren die Pfade eventuell nicht. Auch sollten abhängige Dateien, die im Skript eingelesen werden, nicht quer über die Festplatten verstreut liegen und mit absoluten Pfaden darauf zugegriffen werden.

Eine Möglichkeit ist, in RStudio ein Projekt anzulegen. Wenn ich das Projekt öffne, wird das Arbeitsverzeichnis auf den Projektordner gesetzt. Alle Dateien, die benötigt werden, sollten sich in dem Ordner oder Unterordnern befinden.

Möchte man auf Projekte verzichtet, dann empfielt sich das setwd, welches in das Verzeichnis des Skripts wechselt, ganz am Anfang vom Skript, also gut sichtbar, zu platzieren. Dann nur noch mit relativen Pfaden auf Unterordner zugreifen.

Insbesondere das Vorhalten aller Daten in Unterverzeichnissen ist aber nicht immer praktikabel. Bei meiner Arbeit haben wir zum Beispiel mehrere zentrales Verzeichnisse, in denen z.B. die SAP-Daten liegen. Hier empfielt es sich, die benötigten Verzeichnisse am Anfang vom R-Skript als Variablen zu definieren, die bei Bedarf angepasst werden können. Wir sind sogar noch einen Schritt weitergegangen und ich habe ein Package geschrieben, dass die Verzeichnisse per get-Funktionen, also z.B. getSAPDirectory(), zurückgibt. Sollten sich die Verzeichnisse oder Netzwerkpfade einmal ändern, gibt es eine neue Version des Packages.

R-Beispielcode für getwd und setwd

Ein Verzeichnis durchsuchen

Eine Liste (technisch: ein Vektor) mit den Dateinamen eines Verzeichnisses zu bekommen, ist in R total simpel. Es genügt der Befehl list.files, der verschiedene nützliche Parameter bietet. Die drei wichtigsten habe ich hier aufgelistet, insbesondere die Angabe eines regulären Ausdrucks mittels pattern ist sehr hilfreich, sofern man Dateien systematisch benannt hat. Man denke an alle Exceldateien, die mit “Quartalsreporting” beginnen und 2019 enthalten.

  • path gibt den zu durchsuchenden Pfad an. Ist kein Pfad angegeben, wird das aktuelle Arbeitsverzeichnis (siehe getwd und setwd) genommen.
  • pattern ermöglicht es, die Dateiliste mittels regulärer Ausdrücke zu filtern. Reguläre Ausdrücke sind eine in der IT übliche und sehr flexible Methode, Mengen von Strings zu beschreiben. Reguläre Ausdrücke können recht komplex sein, häufig kommt man aber mit einem einfachen Ausdruck zu Rande. Mehr über reguläre Ausdrücke findet ihr im Kapitel Strings.
  • recursive bezieht die Unterordner in die Suche mit ein, d.h. alle Dateien aus den Unterordnern werden als relativer Pfad (von der Form Unterordner/Dateiname oder Unterordner/Unterordner/Dateiname) mit ausgegeben.

Alle Parameter zum Befehl list.files findet ihr in meinem kostenlosen R-Glossar, in dem eine ständig wachsende Zahl R-Befehle mit Erklärung und Beispielen aufgelistet sind.

Es gibt übrigens auch den Befehl list.dirs, der entsprechend nur die Verzeichnisse zurückgibt. Allerdings gibt es weniger Parameter, nur path, full.names (default=TRUE) und recursive (default=TRUE) sind möglich. So werden z.B. immer die versteckten Ordner angezeigt, bei list.files gibt es dafür den Parameter all.files (default = FALSE).

 

Verzeichnisse prüfen, erstellen und löschen

Um zu prüfen, ob ein Verzeichnis existiert, gibt es den Befehl dir.exists. Um ein Verzeichnis anzulegen, benutzt man dir.create.

Nur der Lösch-Befehl fällt etwas aus dem Rahmen und heißt unlink. Wichtig bei unlink ist, dass der Parameter recursive=TRUE gesetzt wird. Das hängt damit zusammen, dass unlink auch für Dateien verwendet werden kann. Auch ein leeres Verzeichnis kann nicht gelöscht werden, wenn recursive=FALSE. Der Rückgabewert, der zwar nicht wiedergegeben wird, aber per Variable abgefangen werden kann (siehe Skript), ist bei Erfolg 0, bei Fehler 1. Allerdings gilt das Fehlen des Verzeichnisses nicht als Fehler. Konnte das Verzeichnis hingegen nicht gelöscht werden, weil die Berechtigung fehlt oder es aktuell in Verwendung ist (z.B. wenn eine Datei aus dem Verzeichnis durch ein Programm geöffnet ist), dann gibt unlink 1 als Wert zurück.

Dateien in R erstellen, kopieren, umbennen und löschen

Was können wir mit Dateien anstellen? Na ja, prüfen, ob sie existieren, erstellen, kopieren, umbenennen oder löschen. Wobei das Erstellen ohne Zusammenhang eher selten vorkommt, meist schreibt man dann doch direkt eine csv- oder xlsx-Datei oder wenigstens Text in die Datei. Wie man letzteres macht, erfahrt ihr weiter unten im nächsten Abschnitt.

An sich sind die Befehle ziemlich klar aufgebaut, nämlich file.xxx. Alle Befehle nehmen nicht nur einzelne Strings mit einem Dateinamen entgegen sondern auch Vektoren von Dateinamen und führen den Befehl für alle darin enthaltenen Dateien aus.

  • file.exists prüft, ob eine oder mehrere Dateien bereits existieren und gibt dementsprechend einen Vektor mit TRUE oder FALSE zurück
  • file.create erzeugt eine leere Datei bzw. überschreibt eine existierende, sofern der Parameter overwrite=TRUE gesetzt wird.
  • file.rename benennt Dateien um
  • file.remove entfernt eine oder mehrere Dateien. Als Rückgabewert wird TRUE oder FALSE zurückgegeben, je nachdem ob eben das Löschen geklappt hat oder nicht
  • file.copy kopiert Dateien. Dabei gibt es die Parameter overwrite, copy.mode und copy.date. Overwrite sorgt dafür, dass eine schon existierende Datei überschrieben wird, mit copy.mode=TRUE kopiert R die Berechtigungen mit (Lese-/Schreib-Einschränkungen) und mit copy.date=TRUE wird das Erstellungsdatum der ursprünglichen Datei kopiert.

Datei-Informationen wie Datum oder Größe in R

Als letztes zeige ich euch noch, wie ihr Informationen zu Dateien in R auslesen könnt. Die wichtigsten sind vermutlich Dateigröße oder Änderungsdatum. So könnte man das Änderungsdatum checken, um zu sehen, ob es ein Update der Datei gab und es sich lohnt, den Inhalt einzulesen. Die R-Funktionen dafür sind überschaubar denn eigentlich gibt es nur file.info. Die weiteren hier aufgezählten Funktionen rufen file.info auf, sind aber eventuell bequemer in der Anwendung.

Ein bisschen tricky sind die Berechtigungen (Spalte mode), da diese kodiert sind und man sich die Werte herauspulen muss. Andererseits benötigt man diese Details eher selten.

    • file.info liefert einen data.frame mit 7 Spalten, jede Zeile entspricht einer angegeben Datei.
      • size: Die Dateigröße in Bytes
      • isdir: Handelt es sich um ein Verzeichnis
      • mode: gibt eine dreistellige Oktalzahl mit den Rechten zurück. Das Ganze ist ein bisschen kompliziert. Die drei Ziffern stehen für den Besitzer der Datei, die Gruppe, der der Besitzer angehört und jeder. Eine Ziffer setzt sich zusammen aus Lesen (Wert 4), Schreiben (Wert 2) und Ausführen (Wert 1), also z.B. 6 für Lese- und Schreibzugriff.
      • mtime: Zeitstempel, wann die Datei das letzte Mal geändert wurde
      • ctime: Zeitstempel, wann der Status der Datei das letzte Mal geändert wurde, also z.B. durch chmod auf Unix. Unter Windows entspricht das dem Erstellungsdatum.
      • atime: Zeitstempel, wann das letzte Mal auf die Datei zugegriffen wurde
      • exe: Die Ausführbarkeit auf Windows-PCs. Mögliche Werte sind „no“, „msdos“,“win16″, „win32“, „win64“ und „unknown“
    • file.access testet eine Datei auf die Zugriffsmöglichkeiten, welche man mit dem Parameter mode definiert. Dabei wird 0 für Erfolg und -1 für Misserfolg zurückgegeben
      • 0: Existenz (default)
      • 1: Ausfürbarkeit
      • 2: Schreibrechte
      • 3: Leserechte
  • file.mtime gibt einen Zeitstempel (POSIXct) zurück, wann die Datei zum letzten Mal modifiziert wurde
  • file.size gibt die Größe der Datei in Bytes zurück. Um zur nächst größeren Einheit (Kilobyte, Megabyte, Gigabyte, …) zu kommen, muss man die Zahl durch 1024 teilen. Zur Illustration habe ich unten im Beispiel eine Funktion geschrieben, die einen Parameter für die Größeneinheit entgegennimmt.

 

 

So, nun seid ihr Experten in Sachen Verzeichnisse und Dateien. Ich bin gespannt, welche tollen Projekte und Automatisierungen ihr damit umsetzt.

Happy coding,
Euer Holger