Cél és előfeltétel
A parancssori eszközök egyik erőssége, hogy összekapcsolhatók. Egy program eredménye a másik bemenetévé válhat, ha a kimenet nem keveredik össze véletlen állapotüzenetekkel. Ebben a leckében sorokat olvasol és külön kezeled a rendes eredményt a diagnosztikától. A fő kérdés: milyen adatot vár a következő program ugyanazon a csatornán?
Előfeltétel az argumentumok és használati útmutató, valamint a szeletek és nézetek. A teljes edition 2024 példák memóriabeli bemenetet használnak. Nem várnak gépelésre, így minden futásuk ugyanolyan eredményt ellenőriz. A valódi standard bemenet ehhez hasonló olvasási felületet ad, de érkezésének időzítését a felhasználó vagy másik folyamat határozza meg.
A csatornák szerepét tartsd külön
A standard bemenet a bejövő adat alapértelmezett csatornája. A standard kimenetre kerül az eszköz hasznos eredménye. A standard hibakimenet a hibamagyarázatok és szükséges diagnosztika helye. A két kimenet terminálban gyakran egymás mellett látszik, de átirányításkor elválasztható. Emiatt egy sor elé írt „feldolgozás indul” üzenet tönkreteheti a másik program által olvasott adatfolyamot.
A felhasználó által kért súgó viszont lehet rendes, sikeres kimenet. Hibás argumentum esetén a magyarázat és rövid használati emlékeztető a hibakimenetre illik. Nincs minden programra érvényes egyetlen formátum, de a saját eszközöd szerződésének következetesnek kell lennie. A példáinkban két külön pufferrel ellenőrizzük, melyik szöveg melyik helyre került.
Egy sor olvasása állapotot módosít
A read_line a megadott String végéhez fűzi az olvasott adatot, és siker esetén a beolvasott bájtok számát adja vissza. Ha ugyanazt a puffert új sorhoz használod, és különálló sort szeretnél, előbb ürítened kell. A sorvég is benne maradhat a pufferben. Ezért a két egymás utáni olvasást nem elég úgy elképzelni, mint egy változó automatikus lecserélését.
A sikeresen visszaadott nulla bájt az adatfolyam végét jelezheti, nem egy külön üres sort. Egy üres sorhoz ugyanis sorvég tartozik, amelynek van bájthossza. A különbséget a feladatban két sor után még egy olvasással ellenőrizzük. Nem az üres String tartalmából találgatjuk önmagában, hogy a forrás véget ért-e.
Tesztelhető I/O felület
A Cursor egy memóriabeli bájtsorozatot kínál olvasható forrásként. Emiatt ugyanazt az olvasási műveletet tudod kipróbálni, amelyet később fájlon vagy standard bemeneten használnál. A kimenet pedig írható bájtvektor lehet. A függvényekben szereplő impl BufRead és impl Write azt mondja: a konkrét tároló helyett a szükséges olvasási vagy írási képességet kérjük.
A kérdőjel a Result hibáját továbbadja a hívónak, nem hallgatja el. Olvasás és írás is hibázhat, ezért az eredményt vissza kell vezetni a függvény szerződésébe. A memóriabeli sikeres példa nem igazol minden terminál- vagy fájlrendszerhibát; azt igazolja, hogy az adat és a formátum a megadott bemeneten megfelelő. A külső környezet majd további ellenőrzést kíván.
1. feladat: két sor, majd a forrás vége
Olvass két sort egy Cursor értékből. Minden olvasás előtt ürítsd a közös puffert, és ellenőrizd a teljes tartalmat a sorvéggel együtt. A harmadik olvasás már nullát adjon. Így egyszerre látszik a puffer újrafelhasználása és az adatfolyam végét jelző eredmény. A visszaadott bájtszámot se hagyd figyelmen kívül.
Megoldás
use std::io::{self, BufRead, Cursor};
fn main() -> io::Result<()> {
let mut bemenet = Cursor::new(b"alma\nkorte\n");
let mut sor = String::new();
assert_eq!(bemenet.read_line(&mut sor)?, 5);
assert_eq!(sor, "alma\n");
sor.clear();
assert_eq!(bemenet.read_line(&mut sor)?, 6);
assert_eq!(sor, "korte\n");
sor.clear();
assert_eq!(bemenet.read_line(&mut sor)?, 0);
assert!(sor.is_empty());
Ok(())
}
Az itt választott szövegekben minden betű egy bájt. A bájtszám a sorvéget is tartalmazza, tehát nem pusztán a látható betűk számát ellenőrizzük.
2. feladat: nem üres sorok továbbírása
Írj feldolgozót, amely soronként olvas, a teljesen üres sorokat kihagyja, a többit sorvéggel együtt a kimenetre írja. A szóközös sort ne töröld: ebben a szabályban az nem üres. Használd a lines műveletet, amely a sorvégi lezárást leválasztja. Ellenőrizd a kimeneti bájtokat, a továbbírt sorok számát és az üres forrást is.
Megoldás
use std::io::{self, BufRead, Cursor, Write};
fn masol(bemenet: impl BufRead, kimenet: &mut impl Write) -> io::Result<usize> {
let mut darab = 0;
for sor in bemenet.lines() {
let sor = sor?;
if sor.is_empty() { continue; }
writeln!(kimenet, "{sor}")?;
darab += 1;
}
Ok(darab)
}
fn main() -> io::Result<()> {
let mut kimenet = Vec::new();
assert_eq!(masol(Cursor::new(b"alma\n\n \nkorte"), &mut kimenet)?, 3);
assert_eq!(kimenet, b"alma\n \nkorte\n");
let mut ures = Vec::new();
assert_eq!(masol(Cursor::new(b""), &mut ures)?, 0);
assert!(ures.is_empty());
Ok(())
}
Az utolsó bemeneti sor lezárás nélkül is feldolgozható. A kimeneti szabályunk minden továbbírt sorhoz egy sorvéget ad, így ez tudatos formázás, nem bájtpontos másolás.
3. feladat: eredmény és hiba külön pufferben
Készíts függvényt, amely sikeres esetben az „eredmény” sort írja a rendes kimenetre. Hibás esetben csak a „Hiba: hibás kérés” sor kerüljön a másik pufferbe. Mindkét helyzetben ellenőrizd mindkét csatornát, mert egy helyes üzenet önmagában nem bizonyítja, hogy nem szivárgott mellé adat a rossz helyre.
Megoldás
use std::io::{self, Write};
fn jelent(ok: bool, ki: &mut impl Write, hiba: &mut impl Write) -> io::Result<()> {
if ok { writeln!(ki, "eredmény")?; }
else { writeln!(hiba, "Hiba: hibás kérés")?; }
Ok(())
}
fn main() -> io::Result<()> {
let (mut ki, mut hiba) = (Vec::new(), Vec::new());
jelent(true, &mut ki, &mut hiba)?;
assert_eq!(ki, "eredmény\n".as_bytes());
assert!(hiba.is_empty());
ki.clear();
jelent(false, &mut ki, &mut hiba)?;
assert!(ki.is_empty());
assert_eq!(hiba, "Hiba: hibás kérés\n".as_bytes());
Ok(())
}
A példában nem a valódi terminált figyeljük. Az elkülönített pufferek egyértelműen, megismételhetően bizonyítják a függvény csatornaválasztását.
Elfogadási feltételek
- A két read_line hívás után a puffer az adott sort tartalmazza, a harmadik olvasás nulla bájttal véget jelez.
- A feldolgozó csak a teljesen üres sorokat hagyja ki, és egységesen sorvéget tesz a többihez.
- Siker esetén a hibapuffer, hiba esetén az eredménypuffer üres marad.
Továbblépés
A feldolgozó már elkülönült az adat konkrét forrásától. A fájlok és útvonalak leckében ezt a szerződést a fájlrendszer fogalmaival kapcsolod össze, az útvonal építését és a tényleges olvasást külön vizsgálva.