Cél és előfeltétel
A sorválogató a standard bemenetről érkező sorok közül csak azokat írja tovább, amelyek tartalmazzák a megadott szövegmintát. Ez egy kis, valóban használható eszköz, de tudatosan szűk szabályokkal. Nem reguláris kifejezést, nem teljes szavas keresést és nem nyelvi hasonlóságot számolunk. Egy nem üres, pontos szövegrészlet jelenlétét vizsgáljuk minden sorban.
Előfeltétel a hibák és kilépési kódok, valamint a standard bemenet és kimenet. Az első három teljes edition 2024 program önellenőrző megoldás, a külön bemutatott utolsó program pedig tényleges parancssori belépési pont. Egyenként használd őket, ne egy fájlba másold össze az összes főprogramot.
Rögzítsd a keresés jelentését
A keresés kis- és nagybetűérzékeny: a Rust és rust eltérő minta. A tartalmazás egy soron belül keres, ezért egy sorvégen átívelő részletet nem talál meg. Nem végzünk Unicode normalizálást sem; vizuálisan hasonló, de eltérően kódolt szövegek különbözhetnek. Ezek a korlátok nem rejtett hibák, hanem a most megvalósított pontos szövegkeresés szerződései.
Az üres mintát elutasítjuk, mert különben minden sor tartalmazná. Találat hiányában viszont a feldolgozás sikeres, csak a kimenet üres. Ebben az eszközben a nulla találat is nullás kilépési állapotot jelent; ez tudatosan nem a grep minden állapotszabályának másolata. A hívónak így nem kell az üres eredményt I/O hibaként kezelnie.
Sorokból rendezett kimenet
A lines művelet leválasztja a sorvégi LF vagy CRLF lezárást, a kimenetet pedig minden találatnál egy LF zárja. Nem bájtpontos másolást készítünk. A sor belső és szélső szóközeit nem vágjuk le, így az eredmény megőrzi a kiválasztott tartalom jelentős részét. Ha egy másik program sorokat vár, ez a következetes lezárás jól használható szerződés.
A soronkénti feldolgozás nem olvassa egyszerre memóriába az egész forrást, de egyetlen nagyon hosszú sor továbbra is jelentős memóriát igényelhet. A lecke kis, beágyazott adatokat és saját terminálpróbát céloz, nem korlátlan méretű hálózati szolgáltatást. Nagy vagy nem megbízható adatnál később bájt- és sorhosszkorlátot is tervezned kellene a választott felhasználáshoz.
A részleges eredmény is számít
Streaming feldolgozásnál egy későbbi olvasási hiba előtt korábbi találatok már kikerülhettek a kimenetre. Nem állítjuk, hogy a művelet egyetlen visszavonható tranzakció. Az eszköz ilyenkor hibás állapottal és hibakimeneti üzenettel jelez, a fogyasztónak pedig ezt figyelembe kell vennie. A kimeneti írás hibája ugyanúgy meghiúsítja a feldolgozást, nem hagyjuk csendben eltűnni.
A tiszta szövegválogatást először I/O nélkül ellenőrizzük. Utána ugyanaz a feltétel kerül az olvasó és író műveletek közé. Végül a belépési pont csak a környezeti csatlakozásokat kezeli: argumentum, standard bemenet, két kimenet, kilépési állapot. Így a nehezen ismételhető terminálhelyzet mögött a lényegi szabály változatlanul közvetlenül vizsgálható marad.
1. feladat: válogatás kész szövegből
Írj függvényt, amely a kapott szöveg minden mintát tartalmazó sorát saját Stringbe gyűjti. Az üres mintát utasítsa el, nulla találatnál üres sikeres eredményt adjon. A Rust és rust különbségét konkrét példával bizonyítsd. Vizsgáld meg a szóközök és a sorvégi lezárás megőrzésére választott szabályt is.
Megoldás
fn valogat(szoveg: &str, minta: &str) -> Result<String, &'static str> {
if minta.is_empty() { return Err("Üres minta."); }
let mut eredmeny = String::new();
for sor in szoveg.lines() {
if sor.contains(minta) { eredmeny.push_str(sor); eredmeny.push('\n'); }
}
Ok(eredmeny)
}
fn main() {
assert_eq!(valogat("Rust\nrust\n Rust könyv ", "Rust"), Ok(String::from("Rust\n Rust könyv \n")));
assert_eq!(valogat("alma\n", "korte"), Ok(String::new()));
assert_eq!(valogat("", "Rust"), Ok(String::new()));
assert!(valogat("adat", "").is_err());
}
A kimenet saját szöveg, ezért a bemenettől függetlenül megtartható. A szóközös sor teljes tartalma átkerül, csak a lezárását egységesítjük.
2. feladat: soronkénti olvasó és író
A feldolgozó fogadjon BufRead forrást és Write célt. Üres mintánál még az olvasás és írás előtt adjon InvalidInput hibát. Számolja meg a továbbírt sorokat, minden olvasási és írási hibát továbbadva. Cursor és bájtvektor segítségével vizsgáld meg a CRLF bemenetet, a lezárás nélküli utolsó sort és az érvénytelen UTF-8 adatot.
Megoldás
use std::io::{self, BufRead, Cursor, Write};
fn futtat(bemenet: impl BufRead, ki: &mut impl Write, minta: &str) -> io::Result<usize> {
if minta.is_empty() { return Err(io::Error::new(io::ErrorKind::InvalidInput, "Üres minta.")); }
let mut darab = 0;
for sor in bemenet.lines() {
let sor = sor?;
if sor.contains(minta) { writeln!(ki, "{sor}")?; darab += 1; }
}
Ok(darab)
}
fn main() -> io::Result<()> {
let mut ki = Vec::new();
assert_eq!(futtat(Cursor::new(b"Rust\r\nrust\r\nRustacean"), &mut ki, "Rust")?, 2);
assert_eq!(ki, b"Rust\nRustacean\n");
let mut ures = Vec::new();
assert_eq!(futtat(Cursor::new(b""), &mut ures, "x")?, 0);
assert_eq!(futtat(Cursor::new(b"adat"), &mut ures, "").unwrap_err().kind(), io::ErrorKind::InvalidInput);
assert!(ures.is_empty());
assert_eq!(futtat(Cursor::new([0xff]), &mut ures, "x").unwrap_err().kind(), io::ErrorKind::InvalidData);
Ok(())
}
A számláló csak sikeres írás után nő. Az előállított kimenet tényleges bájtjait ellenőrizzük, ezért a kiválasztott sorok sorrendjét és lezárását is vizsgáljuk.
3. feladat: sikeres és hibás feldolgozás jelzése
Készíts rövid megjelenítő réteget a feldolgozó eredményéhez. Siker esetén ne írjon plusz összegző sort az eredménycsatornára; csak sikeres ExitCode értéket adjon. Hiba esetén magyarázat kerüljön a külön hibapufferbe és hibás állapot legyen az eredmény. A korábbi adatkimenetet ne próbálja utólag törölni vagy sikeresnek átnevezni.
Megoldás
use std::io::{self, Write};
use std::process::ExitCode;
fn allapot(eredmeny: io::Result<usize>, hiba: &mut impl Write) -> io::Result<ExitCode> {
match eredmeny {
Ok(_) => Ok(ExitCode::SUCCESS),
Err(ok) => { writeln!(hiba, "Hiba: {ok}")?; Ok(ExitCode::FAILURE) }
}
}
fn main() -> io::Result<()> {
let mut hiba = Vec::new();
assert_eq!(allapot(Ok(0), &mut hiba)?, ExitCode::SUCCESS);
assert!(hiba.is_empty());
let ok = io::Error::new(io::ErrorKind::InvalidData, "hibás szöveg");
assert_eq!(allapot(Err(ok), &mut hiba)?, ExitCode::FAILURE);
assert_eq!(hiba, "Hiba: hibás szöveg\n".as_bytes());
Ok(())
}
A nulla találat itt tudatosan siker. A réteg nem is kapja meg az eredménypuffert, így nem tud véletlenül összefoglaló üzenetet keverni a kiválasztott sorok közé.
Valódi parancssori belépési pont
Az alábbi teljes program a feldolgozó működő terminálváltozata. Argumentum nélkül vagy egyetlen --help kapcsolóval súgót ad és sikeres. Pontosan egy nem üres UTF-8 szövegmintával a standard bemenetet olvassa; hibás hívásnál kettes, I/O hibánál egyes állapotot ad. Így a kapcsoló nélküli ellenőrző futás sem vár gépelésre. Ezt a programot csak az előző megoldások külön ellenőrzése után másold a főprogram helyére.
use std::io::{self, BufRead, Write};
use std::process::ExitCode;
fn valogat(bemenet: impl BufRead, ki: &mut impl Write, minta: &str) -> io::Result<()> {
for sor in bemenet.lines() {
let sor = sor?;
if sor.contains(minta) { writeln!(ki, "{sor}")?; }
}
Ok(())
}
fn main() -> ExitCode {
let args: Vec<_> = std::env::args_os().skip(1).collect();
if args.is_empty() || (args.len() == 1 && args[0] == "--help") {
println!("Használat: sorvalogato MINTA\nA sorok a standard bemenetről érkeznek.");
return ExitCode::SUCCESS;
}
if args.len() != 1 {
eprintln!("Hiba: pontosan egy szövegminta szükséges.");
return ExitCode::from(2);
}
let Some(minta) = args[0].to_str().filter(|m| !m.is_empty()) else {
eprintln!("Hiba: nem üres UTF-8 szövegminta szükséges.");
return ExitCode::from(2);
};
let bemenet = io::stdin();
let kimenet = io::stdout();
match valogat(bemenet.lock(), &mut kimenet.lock(), minta) {
Ok(()) => ExitCode::SUCCESS,
Err(ok) => { eprintln!("Hiba: {ok}"); ExitCode::FAILURE }
}
}
A kipróbáláshoz irányíts két rövid sort a programra. A shell idézőjelezésének részletei környezetenként eltérhetnek; az alábbi parancs egy szokásos Unix shell példája. Az eredmény kizárólag a nagy R betűvel kezdődő sor lesz.
printf 'Rust\nrust\n' | cargo run --quiet -- Rust
Elfogadási feltételek
- A válogatás pontos, kis- és nagybetűérzékeny; üres mintát elutasít, nulla találatot sikernek tekint.
- A kiválasztott sorok sorrendje és szóközei megmaradnak, kimeneti lezárásuk LF; a hibás UTF-8 adat hibát ad.
- A tényleges program súgó esetén nem vár bemenetre, hibás hívást és I/O kudarcot eltérő állapottal jelez.
Továbblépés
Elkészült az első összekapcsolható eszközöd. A Rust a gyakorlatban útvonal további szövegkezelési, gyűjteményes és tesztelési feladatokkal bővíti ezt az alapot. Új kapcsoló előtt mindig írd le, milyen új bemenetet engedsz, mi változik a kimeneten, és hogyan ellenőrzöd a korábbi működés megőrzését.