Ganze Ordner kopieren – mit allem, was drin ist
Bisher sagt der Fährmann bei Ordnern höflich, dass er sie noch nicht kopieren kann. Heute lernt er es: Ein Druck auf F5, und ein ganzer Ordner wird kopiert, mit allen Unterordnern, allen Dateien darin und mit denselben Zugriffsrechten wie das Original. Dabei begegnest du wieder einer Funktion, die sich selbst aufruft, und einem kleinen Fragezeichen, das in Rust sehr viel Arbeit spart.
Bevor wir loslegen
Wir machen da weiter, wo Etappe 5 aufgehört hat. Den kompletten Stand findest du dort unter Zwischenstand. Und für diese Etappe gilt ganz besonders: Probier alles in deinem Spielordner aus, nicht in echten Daten.
Wie kopiert man einen Ordner? Ganz einfach, wenn man es in Worten sagt: Leg am Ziel einen neuen, leeren Ordner an. Dann geh alle Einträge im Original durch. Ist es eine Datei, kopier sie. Ist es ein Ordner, dann … kopier den Ordner. Und wie das geht, haben wir ja gerade beschrieben. Die Beschreibung benutzt sich selbst, und genau so sieht auch der Code aus: eine Funktion, die sich für jeden Unterordner selbst aufruft. Das nennt man Rekursion.
Und die Rechte? Unter Linux und macOS hat jede Datei und jeder Ordner
Zugriffsrechte: Wer darf lesen, wer darf schreiben, wer darf ausführen? Ein
Skript, das ausführbar war, soll es nach dem Kopieren auch noch sein. Die gute Nachricht:
fs::copy nimmt die Rechte einer Datei schon von allein mit. Bei Ordnern müssen
wir selbst nachhelfen. Wie man Rechte anzeigt und ändert, lernst du dann in Etappe 7.
Eine Funktion, die Ordner kopiert
Für die Fehlerbehandlung brauchen wir gleich io, den Teil der
Standardbibliothek für Ein- und Ausgabe:
use std::fs;use std::fs; use std::io;use std::path::{Path, PathBuf};
Die neue Funktion kommt ans Ende der Datei, hinter groesse_text:
text.replace('.', ",") }// Kopiert einen ganzen Ordner mit allem, was darin liegt, und liefert die Zahl der Dateien. // Die Rechte jeder Datei und jedes Ordners werden dabei mitgenommen. fn ordner_kopieren(von: &Path, nach: &Path) -> io::Result<u64> { fs::create_dir(nach)?; let mut dateien = 0; for eintrag in fs::read_dir(von)? { let eintrag = eintrag?; let art = eintrag.file_type()?; let ziel = nach.join(eintrag.file_name()); if art.is_symlink() { continue; // Verknüpfungen lassen wir aus } else if art.is_dir() { dateien += ordner_kopieren(&eintrag.path(), &ziel)?; } else { fs::copy(eintrag.path(), &ziel)?; // nimmt die Rechte der Datei mit dateien += 1; } } // Die Rechte des Ordners erst zum Schluss setzen: Ein schreibgeschützter // Ordner ließe sich sonst gar nicht mehr befüllen. fs::set_permissions(nach, fs::metadata(von)?.permissions())?; Ok(dateien) }
Bevor wir sie Zeile für Zeile anschauen, zum auffälligsten Zeichen darin: dem Fragezeichen.
Fast jede Zeile hier kann schiefgehen. Der Zielordner lässt sich nicht anlegen, ein Eintrag
ist nicht lesbar, die Festplatte ist voll. Jedes Mal liefert Rust ein Result.
In Etappe 5 haben wir so etwas mit match behandelt. Das wären hier aber sieben
match-Blöcke, und die Funktion wäre kaum noch lesbar. Das ? ist die
Abkürzung dafür: „Hat es geklappt, nimm das Ergebnis und mach weiter. Wenn nicht, brich die
ganze Funktion ab und gib den Fehler an den Aufrufer weiter.“ Damit das geht, muss die
Funktion selbst ein Result zurückgeben. Das ist
io::Result<u64>: im Erfolgsfall die Zahl der kopierten Dateien, sonst ein
Ein-/Ausgabe-Fehler.
Jetzt die Geschichte der Funktion:
- Zielordner anlegen:
fs::create_dir(nach)? - Alle Einträge durchgehen:
fs::read_dir(von)?öffnet den Ordner, und jeder einzelne Eintrag ist wieder einResult, daherlet eintrag = eintrag?;. Hier „überschatten“ wir den Namen: Aus demResultnamenseintragwird der echte Eintrag mit demselben Namen. - Was ist es?
file_type()sagt, ob es ein Ordner, eine Datei oder eine Verknüpfung (ein Symlink) ist. Verknüpfungen lassen wir aus, denn eine Verknüpfung auf einen übergeordneten Ordner würde uns sonst endlos im Kreis kopieren lassen. - Ordner: Die Funktion ruft sich selbst auf, für den Unterordner und sein Gegenstück am Ziel. Was dabei an Dateien zusammenkommt, zählen wir dazu.
- Datei:
fs::copykopiert sie samt ihren Rechten, und wir zählen eins hoch. - Rechte des Ordners: Ganz zum Schluss übertragen wir die Rechte des Originalordners auf die Kopie. Warum erst jetzt? Wäre der Originalordner schreibgeschützt und wir setzten das gleich am Anfang, dürften wir in die Kopie nichts mehr hineinlegen.
- Fertig:
Ok(dateien)meldet den Erfolg mit der Anzahl.
cargo run
Äußerlich ändert sich nichts. Rust warnt, dass ordner_kopieren nie
benutzt wird. Gleich bauen wir sie ein.Einbauen und absichern
Jetzt bauen wir die neue Funktion in kopieren ein. Dabei ändert sich die
Reihenfolge der Prüfungen, und es kommt eine ganz neue dazu:
let zielordner = ziel.ordner.display().to_string();self.meldung = if ist_ordner { format!("„{}“ ist ein Ordner. Ordner kann der Fährmann noch nicht kopieren.", name) } else if nach.exists() { format!("„{}“ gibt es in {} schon. Nichts überschrieben.", name, zielordner) } else {self.meldung = if nach.exists() { format!("„{}“ gibt es in {} schon. Nichts überschrieben.", name, zielordner) } else if ist_ordner && nach.starts_with(&von) { format!("„{}“ kann nicht in sich selbst kopiert werden.", name) } else if ist_ordner { match ordner_kopieren(&von, &nach) { Ok(anzahl) => format!( "Ordner kopiert: {} mit {} Dateien → {}", name, anzahl, zielordner ), Err(fehler) => format!("Ordner kopieren fehlgeschlagen: {}", fehler), } } else {match fs::copy(&von, &nach) {
- Schon da? Diese Prüfung steht jetzt ganz vorn, denn sie gilt für Dateien und Ordner gleichermaßen: Was es am Ziel schon gibt, wird nicht überschrieben.
- In sich selbst? Stell dir vor, links steht der Balken auf dem Ordner
Fotos, und rechts bist du inFotoshineingegangen. Dann sollte der FährmannFotosnachFotos/Fotoskopieren. Beim Kopieren würde er dort aber die neue Kopie wiederfinden und auch sie kopieren, nachFotos/Fotos/Fotos, und so weiter, bis die Festplatte voll ist.nach.starts_with(&von)erkennt diese Falle: Beginnt der Zielpfad mit dem Quellpfad, liegt das Ziel innerhalb der Quelle. - Ordner kopieren: Ein
matchüber das Ergebnis, genau wie bei den Dateien. Weil die Erfolgsmeldung lang ist, verteiltformat!sie auf mehrere Zeilen. Das ist erlaubt, Rust achtet nur auf die Kommas.
ordner_kopieren
dank ? sofort ab, und die Meldungszeile sagt, was passiert ist. Was bis dahin
kopiert war, bleibt am Ziel liegen. Profi-Programme würden die halbe Kopie wieder aufräumen,
aber oft will man sie gerade sehen, um den Fehler zu verstehen. Für unseren Fährmann ist
Ehrlichkeit hier wichtiger als Perfektion.match fs::copy(…) und alles darunter bleiben unverändert.cargo run
Geh im rechten Panel in einen leeren Ordner, setz links den Balken auf einen
Ordner mit Unterordnern und drück F5. Die Meldungszeile sagt, wie viele Dateien
kopiert wurden, und rechts erscheint der neue Ordner. Geh mit Enter hinein: Alles
ist da. Probier dann die Falle aus: Geh rechts in den Ordner hinein, den du links markiert
hast, und drück F5. Der Fährmann weigert sich zu Recht. Wenn du die Rechte prüfen
willst, vergleich im Terminal ls -l von Original und Kopie: Die Buchstaben ganz
links sind gleich.
Der komplette Code nach Etappe 6
Hier ist der vollständige Stand nach Etappe 6. Nimm ihn zum Vergleichen, wenn irgendwo ein Fehler steckt, den du nicht findest, oder als Einstiegspunkt, wenn du erst bei der nächsten Etappe dazustößt.
Cargo.toml und src/main.rs anzeigen
Cargo.toml
[package]
name = "faehrmann"
version = "0.1.0"
edition = "2024"
[dependencies]
iced = "0.14"
src/main.rs
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use iced::widget::{column, container, row, text};
use iced::keyboard::{self, Key, key::Named};
use iced::{Background, Border, Color, Element, Fill, Font, Subscription, Task, Theme};
// Die Farben des Norton Commander
const BLAU: Color = Color::from_rgb8(0, 0, 170);
const TUERKIS: Color = Color::from_rgb8(0, 170, 170);
const SCHWARZ: Color = Color::BLACK;
const WEISS: Color = Color::WHITE;
const HELLTUERKIS: Color = Color::from_rgb8(85, 255, 255);
const GELB: Color = Color::from_rgb8(255, 255, 85);
// So viele Einträge passen untereinander in ein Panel
const ZEILEN: usize = 24;
// Ein Eintrag in einem Ordner: eine Datei oder ein Unterordner.
struct Eintrag {
name: String,
ist_ordner: bool,
groesse: u64,
}
fn main() -> iced::Result {
iced::application(Faehrmann::neu, Faehrmann::update, Faehrmann::view)
.subscription(Faehrmann::subscription)
.title("Fährmann")
.default_font(Font::MONOSPACE)
.window_size((1000.0, 640.0))
.run()
}
// Das Gedächtnis des Fährmanns: welcher Ordner offen ist und was darin liegt.
struct Faehrmann {
links: Panel,
rechts: Panel,
aktiv: Seite,
meldung: String, // was der Fährmann zuletzt zu sagen hatte
}
// Welches der beiden Panels ist gerade aktiv?
#[derive(PartialEq)]
enum Seite {
Links,
Rechts,
}
#[derive(Debug, Clone)]
enum Message {
Hoch,
Runter,
Oeffnen,
Wechseln,
Kopieren,
Beenden,
}
impl Faehrmann {
// Startet im Ordner, aus dem das Programm aufgerufen wurde.
fn neu() -> Self {
let ordner = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("/"));
Faehrmann {
links: Panel::neu(ordner.clone()),
rechts: Panel::neu(ordner),
aktiv: Seite::Links,
meldung: String::from("Willkommen an Bord! Tab wechselt das Panel, F5 kopiert, F10 beendet."),
}
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Hoch => self.aktives_panel().hoch(),
Message::Runter => self.aktives_panel().runter(),
Message::Oeffnen => self.aktives_panel().oeffnen(),
Message::Wechseln => {
self.aktiv = match self.aktiv {
Seite::Links => Seite::Rechts,
Seite::Rechts => Seite::Links,
};
}
Message::Kopieren => self.kopieren(),
Message::Beenden => return iced::exit(),
}
Task::none()
}
// Abonniert die Tastatur: Jeder Tastendruck geht durch die Funktion taste.
fn subscription(&self) -> Subscription<Message> {
keyboard::listen().filter_map(taste)
}
// Liefert das Panel, das gerade aktiv ist, zum Verändern.
fn aktives_panel(&mut self) -> &mut Panel {
match self.aktiv {
Seite::Links => &mut self.links,
Seite::Rechts => &mut self.rechts,
}
}
// Liefert das Panel, das gerade NICHT aktiv ist, zum Verändern.
fn inaktives_panel(&mut self) -> &mut Panel {
match self.aktiv {
Seite::Links => &mut self.rechts,
Seite::Rechts => &mut self.links,
}
}
// Liefert beide Panels zum Anschauen: zuerst das aktive, dann das andere.
fn quelle_und_ziel(&self) -> (&Panel, &Panel) {
match self.aktiv {
Seite::Links => (&self.links, &self.rechts),
Seite::Rechts => (&self.rechts, &self.links),
}
}
// Kopiert die markierte Datei vom aktiven Panel in den Ordner des anderen Panels.
fn kopieren(&mut self) {
let (quelle, ziel) = self.quelle_und_ziel();
let Some(eintrag) = quelle.markierter_eintrag() else {
return;
};
if eintrag.name == ".." {
return;
}
// Alles, was wir brauchen, herausschreiben, solange wir die Panels ausleihen
let name = eintrag.name.clone();
let ist_ordner = eintrag.ist_ordner;
let von = quelle.ordner.join(&name);
let nach = ziel.ordner.join(&name);
let zielordner = ziel.ordner.display().to_string();
self.meldung = if nach.exists() {
format!("„{}“ gibt es in {} schon. Nichts überschrieben.", name, zielordner)
} else if ist_ordner && nach.starts_with(&von) {
format!("„{}“ kann nicht in sich selbst kopiert werden.", name)
} else if ist_ordner {
match ordner_kopieren(&von, &nach) {
Ok(anzahl) => format!(
"Ordner kopiert: {} mit {} Dateien → {}",
name, anzahl, zielordner
),
Err(fehler) => format!("Ordner kopieren fehlgeschlagen: {}", fehler),
}
} else {
match fs::copy(&von, &nach) {
Ok(_) => format!("Kopiert: {} → {}", name, zielordner),
Err(fehler) => format!("Kopieren fehlgeschlagen: {}", fehler),
}
};
self.inaktives_panel().neu_lesen();
}
fn view(&self) -> Element<'_, Message> {
let menuzeile = container(text(" Links Datei Befehle Optionen Rechts"))
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(TUERKIS)),
text_color: Some(SCHWARZ),
..container::Style::default()
});
let panels = row![
self.links.ansicht(self.aktiv == Seite::Links),
self.rechts.ansicht(self.aktiv == Seite::Rechts),
]
.spacing(6);
let arbeitsflaeche = container(panels)
.width(Fill)
.height(Fill)
.padding(6)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(BLAU)),
text_color: Some(WEISS),
..container::Style::default()
});
let meldungszeile = container(text(self.meldung.as_str()).color(WEISS))
.width(Fill)
.padding([0, 6])
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(SCHWARZ)),
..container::Style::default()
});
let tastenleiste = row![
f_taste(1, "Hilfe"),
f_taste(2, "Menü"),
f_taste(3, "Ansicht"),
f_taste(4, "Bearb."),
f_taste(5, "Kopie"),
f_taste(6, "Versch."),
f_taste(7, "Ordner"),
f_taste(8, "Lösch."),
f_taste(9, "Leiste"),
f_taste(10, "Ende"),
]
.spacing(4);
let tastenleiste = container(tastenleiste)
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(SCHWARZ)),
..container::Style::default()
});
column![menuzeile, arbeitsflaeche, meldungszeile, tastenleiste].into()
}
}
// Ein Panel: ein Ordner, seine Einträge und der Auswahlbalken.
struct Panel {
ordner: PathBuf,
eintraege: Vec<Eintrag>,
cursor: usize, // Nummer des markierten Eintrags
erste_zeile: usize, // Nummer des obersten sichtbaren Eintrags
}
impl Panel {
// Legt ein Panel an, das den angegebenen Ordner zeigt.
fn neu(ordner: PathBuf) -> Self {
let eintraege = ordner_lesen(&ordner);
Panel {
ordner,
eintraege,
cursor: 0,
erste_zeile: 0,
}
}
// Balken eine Zeile nach oben
fn hoch(&mut self) {
if self.cursor > 0 {
self.cursor -= 1;
}
self.sichtbar_halten();
}
// Balken eine Zeile nach unten
fn runter(&mut self) {
if self.cursor + 1 < self.eintraege.len() {
self.cursor += 1;
}
self.sichtbar_halten();
}
// Liefert den Eintrag, auf dem der Balken steht, falls es einen gibt.
fn markierter_eintrag(&self) -> Option<&Eintrag> {
self.eintraege.get(self.cursor)
}
// Liest den Ordner neu ein, zum Beispiel nachdem eine Datei dazugekommen ist.
fn neu_lesen(&mut self) {
self.eintraege = ordner_lesen(&self.ordner);
if self.cursor >= self.eintraege.len() {
self.cursor = self.eintraege.len().saturating_sub(1);
}
self.sichtbar_halten();
}
// Schiebt den sichtbaren Ausschnitt so, dass der Balken immer zu sehen ist.
fn sichtbar_halten(&mut self) {
if self.cursor < self.erste_zeile {
self.erste_zeile = self.cursor;
}
if self.cursor >= self.erste_zeile + ZEILEN {
self.erste_zeile = self.cursor + 1 - ZEILEN;
}
}
// Betritt den markierten Ordner, oder geht bei ".." eine Ebene hoch.
fn oeffnen(&mut self) {
let Some(eintrag) = self.eintraege.get(self.cursor) else {
return;
};
if !eintrag.ist_ordner {
return; // Dateien öffnen wir (noch) nicht
}
let neuer_ordner = if eintrag.name == ".." {
match self.ordner.parent() {
Some(oben) => oben.to_path_buf(),
None => return,
}
} else {
self.ordner.join(&eintrag.name)
};
self.eintraege = ordner_lesen(&neuer_ordner);
self.ordner = neuer_ordner;
self.cursor = 0;
self.erste_zeile = 0;
}
// Zeichnet das Panel. Nur das aktive Panel zeigt den Balken und einen hellen Kopf.
fn ansicht(&self, aktiv: bool) -> Element<'_, Message> {
let kopffarbe = if aktiv { SCHWARZ } else { GELB };
let kopf = container(text(self.ordner.display().to_string()).color(kopffarbe))
.style(move |_theme: &Theme| container::Style {
background: if aktiv {
Some(Background::Color(TUERKIS))
} else {
None
},
..container::Style::default()
});
let mut liste = column![];
for (nr, eintrag) in self
.eintraege
.iter()
.enumerate()
.skip(self.erste_zeile)
.take(ZEILEN)
{
liste = liste.push(zeile(eintrag, aktiv && nr == self.cursor));
}
container(column![kopf, liste].spacing(6))
.width(Fill)
.height(Fill)
.padding(8)
.clip(true)
.style(|_theme: &Theme| container::Style {
border: Border {
color: TUERKIS,
width: 2.0,
radius: 0.0.into(),
},
..container::Style::default()
})
.into()
}
}
// Baut eine einzelne Taste der Funktionstastenleiste, z. B. "5 Kopie".
fn f_taste(nummer: u8, beschriftung: &'static str) -> Element<'static, Message> {
let zahl = text(nummer).color(WEISS);
let feld = container(text(beschriftung))
.width(Fill)
.style(|_theme: &Theme| container::Style {
background: Some(Background::Color(TUERKIS)),
text_color: Some(SCHWARZ),
..container::Style::default()
});
row![zahl, feld].width(Fill).into()
}
// Liest einen Ordner und liefert seine Einträge: oben "..", dann Ordner, dann Dateien.
fn ordner_lesen(pfad: &Path) -> Vec<Eintrag> {
let mut eintraege = Vec::new();
let Ok(inhalt) = fs::read_dir(pfad) else {
return eintraege; // Ordner nicht lesbar: leere Liste
};
for eintrag in inhalt.flatten() {
let name = eintrag.file_name().to_string_lossy().to_string();
if name.starts_with('.') {
continue; // versteckte Dateien überspringen
}
let Ok(info) = eintrag.metadata() else {
continue; // keine Infos lesbar: Eintrag überspringen
};
eintraege.push(Eintrag {
name,
ist_ordner: info.is_dir(),
groesse: info.len(),
});
}
// Ordner vor Dateien, innerhalb davon alphabetisch
eintraege.sort_by(|a, b| {
b.ist_ordner
.cmp(&a.ist_ordner)
.then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))
});
// Der Weg nach oben steht immer ganz oben
if pfad.parent().is_some() {
let hoch = Eintrag {
name: String::from(".."),
ist_ordner: true,
groesse: 0,
};
eintraege.insert(0, hoch);
}
eintraege
}
// Baut eine Zeile im Panel: links der Name, rechts die Größe.
fn zeile(eintrag: &Eintrag, markiert: bool) -> Element<'_, Message> {
let farbe = if markiert {
SCHWARZ
} else if eintrag.ist_ordner {
WEISS
} else {
HELLTUERKIS
};
let groesse = if eintrag.ist_ordner {
String::from("<ORDNER>")
} else {
groesse_text(eintrag.groesse)
};
let inhalt = row![
text(eintrag.name.as_str())
.color(farbe)
.width(Fill)
.wrapping(text::Wrapping::None),
text(groesse).color(farbe),
]
.spacing(12);
// Der markierte Eintrag bekommt den türkisen Balken
container(inhalt)
.width(Fill)
.style(move |_theme: &Theme| container::Style {
background: if markiert {
Some(Background::Color(TUERKIS))
} else {
None
},
..container::Style::default()
})
.into()
}
// Macht aus einer Zahl von Bytes etwas Lesbares, zum Beispiel "12,3 KB".
fn groesse_text(bytes: u64) -> String {
let b = bytes as f64;
let text = if bytes < 1024 {
format!("{} B", bytes)
} else if b < 1024.0 * 1024.0 {
format!("{:.1} KB", b / 1024.0)
} else if b < 1024.0 * 1024.0 * 1024.0 {
format!("{:.1} MB", b / 1024.0 / 1024.0)
} else {
format!("{:.1} GB", b / 1024.0 / 1024.0 / 1024.0)
};
text.replace('.', ",")
}
// Kopiert einen ganzen Ordner mit allem, was darin liegt, und liefert die Zahl der Dateien.
// Die Rechte jeder Datei und jedes Ordners werden dabei mitgenommen.
fn ordner_kopieren(von: &Path, nach: &Path) -> io::Result<u64> {
fs::create_dir(nach)?;
let mut dateien = 0;
for eintrag in fs::read_dir(von)? {
let eintrag = eintrag?;
let art = eintrag.file_type()?;
let ziel = nach.join(eintrag.file_name());
if art.is_symlink() {
continue; // Verknüpfungen lassen wir aus
} else if art.is_dir() {
dateien += ordner_kopieren(&eintrag.path(), &ziel)?;
} else {
fs::copy(eintrag.path(), &ziel)?; // nimmt die Rechte der Datei mit
dateien += 1;
}
}
// Die Rechte des Ordners erst zum Schluss setzen: Ein schreibgeschützter
// Ordner ließe sich sonst gar nicht mehr befüllen.
fs::set_permissions(nach, fs::metadata(von)?.permissions())?;
Ok(dateien)
}
// Übersetzt einen Tastendruck in eine Nachricht, oder in gar nichts.
fn taste(ereignis: keyboard::Event) -> Option<Message> {
let keyboard::Event::KeyPressed { key, .. } = ereignis else {
return None; // Taste losgelassen oder Ähnliches: interessiert uns nicht
};
match key {
Key::Named(Named::ArrowUp) => Some(Message::Hoch),
Key::Named(Named::ArrowDown) => Some(Message::Runter),
Key::Named(Named::Enter) => Some(Message::Oeffnen),
Key::Named(Named::Tab) => Some(Message::Wechseln),
Key::Named(Named::F5) => Some(Message::Kopieren),
Key::Named(Named::F10) => Some(Message::Beenden),
_ => None,
}
}
? gibt einen Fehler sofort an den
Aufrufer weiter und erspart viele match-Blöcke, setzt aber voraus, dass die
Funktion selbst ein Result liefert, etwa io::Result<T>. Eine
rekursive Funktion kopiert verschachtelte Ordner beliebiger Tiefe. file_type()
unterscheidet Ordner, Dateien und Verknüpfungen, fs::copy überträgt die Rechte
einer Datei, und mit fs::set_permissions setzen wir sie für Ordner selbst.
starts_with schützt vor dem Kopieren eines Ordners in sich selbst.Zum Ausprobieren
Die Übungen sind freiwillig, aber sie helfen enorm. Mach nach jeder Übung einen Probelauf, und stell danach den alten Zustand wieder her, damit die nächste Etappe bei dir genauso weitergeht wie bei allen anderen.
- Wie groß? Lass
ordner_kopierenstatt der Zahl der Dateien die Zahl der kopierten Bytes liefern und zeig sie mitgroesse_textan. Tipp:fs::copyliefert die Bytes einer Datei. - Verknüpfungen zählen: Der Fährmann überspringt Verknüpfungen stillschweigend. Wie könnte er am Ende sagen, wie viele er ausgelassen hat? Überleg dir, welche Rückgabe dafür nötig wäre.
- Ohne Fragezeichen: Schreib die Zeile
fs::create_dir(nach)?;einmal mitmatchaus. Wie viele Zeilen brauchst du?
Lösungen anzeigen
1. dateien += 1; wird zu bytes += fs::copy(eintrag.path(), &ziel)?;
(die Variable heißt dann besser bytes), und die Rekursion zählt ihre Bytes
genauso dazu. In kopieren: Ok(bytes) => format!("Ordner kopiert: {}
({}) → {}", name, groesse_text(bytes), zielordner).
2. Die Funktion müsste zwei Zahlen liefern, zum Beispiel als Tupel
io::Result<(u64, u64)> mit Dateien und ausgelassenen Verknüpfungen. Bei
der Rekursion zählt man beide Werte des Unterordners dazu:
let (d, v) = ordner_kopieren(…)?; dateien += d; verknuepfungen += v;.
3. Etwa so: match fs::create_dir(nach) { Ok(()) => {} Err(fehler) => return
Err(fehler), }, also vier oder fünf Zeilen statt einer. Und genau das macht das
Fragezeichen für dich. Mehr zu Result und ?:
Buch, Kapitel 52.
Etappe 6 geschafft 🗂️
Der Fährmann kopiert jetzt ganze Ordner, beliebig tief verschachtelt, mit allen Rechten und mit einer Sicherung gegen die gefährlichste Falle. In der nächsten Etappe machen wir die Rechte sichtbar und bauen einen kleinen Dialog im Commander-Stil, mit dem du sie ändern kannst.
Hat dir das Mitbauen Spaß gemacht? Ein Kaffee hilft, dass die nächste Etappe ein Stück schneller fertig wird.
☕ Kaffee spendieren