For å lage et Rust-program som leser filinndata og håndterer ulike kommando-linjeargumenter, kan du bruke clap-biblioteket. Dette biblioteket gir en strukturert måte å definere og validere kommando-linjeargumenter på, noe som gjør det lettere å bygge robust og fleksibelt programvare. I denne delen vil vi gjennomgå hvordan du kan sette opp et slikt program, og vi vil bruke et Rust-program som etterligner funksjonaliteten til det velkjente cut-verktøyet.

Først kopierer vi testene og oppretter nødvendige avhengigheter i Cargo.toml-filen. Her er hvilke crates du bør inkludere:

toml
[dependencies]
anyhow = "1.0.79" clap = { version = "4.5.0", features = ["derive"] } csv = "1.3.0" regex = "1.10.3" [dev-dependencies] assert_cmd = "2.0.13" predicates = "3.0.4" pretty_assertions = "1.4.0" rand = "0.8.5"

csv-crate brukes til å analysere CSV-filer, som er en vanlig format for data. For å begynne med, kjører vi kommandoen cargo test, som vil laste ned de nødvendige avhengighetene og kjøre testene. Alle testene bør mislykkes på dette punktet, men vi vil gradvis implementere funksjonaliteten for å få dem til å passere.

Definering av argumentene

Programmet vårt skal håndtere tre hovedparametere: fields, bytes, og chars. Brukeren må angi én av disse for å spesifisere hvilken type data som skal hentes fra inputfilene. Kommando-linjegrensesnittet vil se slik ut:

sql
$ cargo run -- --help
Rust version of `cut` Usage: cutr [OPTIONS] <--fields |--bytes |--chars > [FILES]... Arguments: [FILES]... Input file(s) [default: -] Options: -d, --delimiter Field delimiter [default: "\t"] -f, --fields Selected fields -b, --bytes Selected bytes -c, --chars Selected chars -h, --help Print help -V, --version Print version

Det er viktig at brukeren må angi akkurat én av --fields, --bytes eller --chars. Inputfilene er valgfri, og standardverdien er STDIN (representert med et bindestrek). Feltavgrensningen er som standard tabulatortegn.

For å gjøre dette mer strukturert, oppretter vi et argumentgrensesnitt ved hjelp av clap:

rust
#[derive(Debug)] struct Args { files: Vec<String>, delimiter: String, extract: ArgsExtract, } #[derive(Debug)] struct ArgsExtract { fields: Option<String>, bytes: Option<String>, chars: Option<String>, }

I denne strukturen representerer files en liste over inputfiler, delimiter er tegnene som skiller feltene i filene, og extract inneholder de spesifikke argumentene for å hente ut enten felt, byte eller tegn.

Parsing av argumentene

Når du har definert argumentene, kan du bruke clap til å analysere de kommando-linjeargumentene som brukeren gir. Her er et eksempel på hvordan du kan bruke clap til å hente disse argumentene:

rust
fn get_args() -> Args {
let matches = Command::new("cutr") .version("0.1.0") .author("Ken Youens-Clark") .about("Rust version of `cut`") .get_matches(); Args { files: vec!["-".to_string()], delimiter: "\t".to_string(), extract: ArgsExtract { fields: None, bytes: None, chars: None, }, } }

Når programmet kjøres uten argumenter, skal det mislykkes med en passende feilmelding, som beskrevet tidligere:

lua
$ cargo run
error: the following required arguments were not provided: <--fields |--bytes |--chars > Usage: cutr <--fields |--bytes |--chars > [FILES]...

Når argumentene derimot er korrekt angitt, skal programmet vise informasjon om de innstilte verdiene, for eksempel:

pgsql
$ cargo run -- -f 1 Args { files: ["-"], delimiter: "\t", extract: ArgsExtract { fields: Some("1"), bytes: None, chars: None }, }

Feilhåndtering og validering

Det er viktig at programmet håndterer ugyldige argumenter korrekt. For eksempel, hvis brukeren prøver å bruke både --fields og --bytes, skal programmet gi en feil:

pgsql
$ cargo run -- -f 1 -b 8-9 tests/inputs/movies1.csv
error: the argument '--fields' cannot be used with '--bytes' Usage: cutr <--fields |--bytes |--chars> [FILES]...

Slike kontroller sikrer at programmet kun behandler de gyldige kombinasjonene av argumenter. For å implementere denne valideringen kan du bruke ArgGroup i clap for å gruppere de gjensidig utelukkende argumentene:

rust
.group(ArgGroup::new("extract")
.args(["fields", "bytes", "chars"])
.
required(true) .multiple(false))

Videre validering av delimiter

Et annet viktig aspekt ved programmet er validering av feltavgrensere. Standardverdien er tabulator (\t), men hvis brukeren ønsker å bruke en annen avgrenser, kan de angi den ved hjelp av -d eller --delimiter. Her kan du utvide funksjonaliteten ved å legge til sjekk for gyldige avgrensere, eller til og med støtte for flere formater som CSV eller TSV.


Når du bygger programmer som håndterer filinnlesing og argumenter, er det viktig å forstå hvordan forskjellige datatyper behandles og valideres. Rust gjør det mulig å bygge sikre og effektive programmer, men det er avgjørende å sørge for at brukerinputen blir validert på en konsekvent måte. Videre bør du ha en klar forståelse av hvordan feil skal håndteres, slik at programmet ikke krasjer uventet eller gir villedende feilmeldinger. Validering og dokumentasjon av argumentene vil også bidra til at brukerne forstår hvordan de kan bruke programmet på riktig måte.

Hvordan hente ut spesifikke data fra tekststrenger og CSV-poster i Rust

Å håndtere tekststrenger og CSV-poster i Rust kan være utfordrende, men kraften til Rusts standardbibliotek og dets feilmeldinger gir en god støtte under utviklingen. Et sentralt tema i behandlingen av tekstdata er effektiv og sikker utvinning av spesifikke deler av strenger eller poster. Dette krever forståelse for hvordan man håndterer indeksering av både tegn, byte og felt. I denne sammenhengen ser vi på ulike metoder for å hente ut deler av tekststrenger og felter i CSV-poster.

Når vi jobber med tekst, er det viktig å forstå forskjellen mellom å hente ut tegn (characters), byte og felt. Å velge tegn innebærer å jobbe med UTF-8-enkodede strenger, mens å hente ut byte kan innebære potensielle problemer med ugyldige UTF-8-kodinger. For CSV-poster er det på sin side avgjørende å vite hvordan man håndterer data i et strukturert format.

I dette avsnittet skal vi se på hvordan funksjoner som extract_chars, extract_bytes og extract_fields kan implementeres for å håndtere disse oppgavene i Rust.

Først tar vi for oss funksjonen extract_chars, som er designet for å hente spesifikke tegn fra en streng. Rust gir oss en kraftig iterator som kan deles opp i tegn ved hjelp av str::chars(). Denne iteratoren lar oss hente individuelle tegn fra en tekststreng, noe som er nyttig når vi ønsker å arbeide med spesifikke posisjoner i en streng.

Funksjonen begynner med å konvertere strengen til en vektor av tegn og deretter itererer over de ønskede indeksene (i form av et intervall, Range). For hvert intervall henter den ut tegnene som svarer til posisjonene i intervallet. Denne prosessen kan gjøres på flere måter, og valget mellom ulike implementasjoner avhenger av hvor mye mutabilitet man ønsker i koden. Her er en av de enkleste måtene å gjøre det på:

rust
fn extract_chars(line: &str, char_pos: &[Range]) -> String {
let chars: Vec<_> = line.chars().collect();
let mut selected: Vec<char> = vec![];
for range in char_pos.iter().cloned() { for i in range {
if let Some(val) = chars.get(i) {
selected.
push(*val) } } } selected.iter().collect() }

Denne metoden bruker et for-løkke for å gå gjennom hvert intervall i char_pos og henter tegnene som er innenfor det angitte området. Resultatet blir deretter samlet inn i en ny streng.

Neste funksjon, extract_bytes, er tilsvarende, men her jobber vi med byte i stedet for tegn. Når vi jobber med bytes, må vi være oppmerksomme på at noen av de valgte byteposisjonene kan føre til ugyldige UTF-8-strenger. Derfor må vi håndtere muligheten for å få et ugyldig resultat på en sikker måte. Her er en implementasjon:

rust
fn extract_bytes(line: &str, byte_pos: &[Range]) -> String {
let bytes = line.as_bytes(); let selected: Vec<_> = byte_pos .iter() .cloned() .flat_map(|range| range.filter_map(|i| bytes.get(i)).copied()) .collect(); String::from_utf8_lossy(&selected).into_owned() }

I denne funksjonen bryter vi strengen ned til en byte-array og henter ut de spesifikke byte-posisjonene som ønskes. Etter det bruker vi String::from_utf8_lossy for å håndtere potensielt ugyldige UTF-8-tegn, som gjør at vi kan unngå krasj i programmet selv om noen byte ikke danner en gyldig streng.

Til slutt ser vi på hvordan man kan hente spesifikke felter fra en CSV-poster, som er en vanlig oppgave når man arbeider med data i tabellformat. Rust har et praktisk bibliotek kalt csv for håndtering av CSV-filer, og et viktig aspekt her er hvordan vi kan hente ut feltene fra en StringRecord-post. Denne prosessen er veldig lik den for å hente ut tegn, men i dette tilfellet handler det om å hente feltene fra CSV-posten basert på spesifiserte indekser.

Her er en funksjon som tar en StringRecord og en liste med intervaller for å hente de ønskede feltene:

rust
fn extract_fields(record: &StringRecord, field_pos: &[Range]) -> Vec<String> { field_pos .iter() .cloned()
.flat_map(|range| range.filter_map(|i| record.get(i)))
.
map(String::from) .collect() }

Denne funksjonen fungerer på en lignende måte som de forrige eksemplene. Den itererer over feltene i CSV-posten og henter ut de som er angitt i de ønskede intervallene. Resultatet blir en vektor med strenger som representerer de valgte feltene.

Når man utvikler programmer som håndterer tekst og CSV-filer, er det viktig å teste disse funksjonene grundig. Unit-tester er en utmerket måte å forsikre seg om at funksjonene fungerer som de skal. Her er et eksempel på en enhetstest for extract_fields-funksjonen:

rust
#[test]
fn test_extract_fields() { let rec = StringRecord::from(vec!["Captain", "Sham", "12345"]); assert_eq!(extract_fields(&rec, &[0..1]), &["Captain"]);
assert_eq!(extract_fields(&rec, &[1..2]), &["Sham"]);
assert_eq!( extract_fields(&rec, &[0..1, 2..3]), &["Captain", "12345"] ); assert_eq!(extract_fields(&rec, &[0..1, 3..4]), &["Captain"]); assert_eq!(extract_fields(&rec, &[1..2, 0..1]), &["Sham", "Captain"]); }

Å sørge for at funksjonene fungerer som forventet, krever grundige tester, og det er viktig å være tålmodig i prosessen. Husk at feilmeldinger i Rust ofte er svært hjelpsomme og kan guide deg mot løsninger.

Endelig er det viktig å merke seg at det finnes mange måter å implementere disse funksjonene på. Det finnes ikke nødvendigvis én "riktig" måte, så lenge funksjonene er effektive og passer godt inn i systemet ditt. Testene er den beste indikatoren på om implementasjonen er riktig.

Hvordan fungerer kommandolinjeverktøy og håndtering av argumenter i Rust-prosjekter?

Å arbeide med kommandolinjeverktøy i Rust innebærer en rekke konsepter knyttet til hvordan programmer leser, tolker og behandler brukerens inndata gjennom argumenter og filer. Et grunnleggende element er forståelsen av hvordan man oppretter og kjører prosjekter med Cargo, Rusts pakkebehandler og byggeverktøy, som gjør det mulig å organisere kildekode, avhengigheter og testskript på en strukturert måte.

Ved håndtering av kommandolinjeargumenter er det avgjørende å definere tydelige parametre for programmet. Biblioteket clap er et sentralt verktøy som brukes til å parsere og validere disse argumentene på en elegant måte. Clap gjør det mulig å spesifisere hvilke argumenter som kreves, valgfrie flagg, og hvordan disse skal tolkes, for eksempel med støtte for både case-sensitive og case-insensitive mønstre. Særlig i versjon 4 av clap, med derive-makroer, har man fått kraftige verktøy for å redusere boilerplate og skrive mer deklarativ kode.

Kommandolinjeverktøy som cat, grep, cut, wc, tail og head gir gode eksempler på hvordan linje- eller bytebasert behandling av filer kan utføres. Disse verktøyene demonstrerer viktigheten av å kunne lese inn data enten fra en fil eller standard input (STDIN), iterere gjennom linjer eller bytes, og skrive ut relevant informasjon, som linjenummer eller utvalgte felter. For eksempel kan cat brukes til å skrive ut innholdet av en fil, mens grep filtrerer linjer basert på regulære uttrykk, med mulighet for å telle forekomster eller ignorere store og små bokstaver.

Ved tekstbehandling er det avgjørende å forstå forskjellen på bytes og tegn, spesielt når man jobber med Unicode. Rust tilbyr verktøy for å håndtere begge deler riktig, og det er essensielt å velge riktig metode for å unngå feil i tekstmanipulering. Når man teller tegn, må man for eksempel ta hensyn til at enkelte Unicode-tegn kan bestå av flere bytes.

Håndtering av filoperasjoner krever også nøye kontroll på hvordan filer åpnes, lukkes, og hvordan eventuelle feil fanges opp og håndteres. Programmet må kunne validere at filer eksisterer og at brukeren har nødvendige rettigheter. Bruken av konstanter, som for eksempel oktal notasjon ved chmod-kommandoer, illustrerer hvordan systemrettigheter kan styres programmatisk.

For å sikre kvalitet på koden benyttes tester aktivt. Rust har et integrert testsystem som gjør det mulig å skrive både enhetstester og integrasjonstester. Det anbefales å starte utviklingen med tester for å sikre korrekt funksjonalitet og forenkle videre utvikling. Dette gjelder også for programmer som skal kjøre på forskjellige plattformer, hvor betinget kompilering gjør det mulig å teste Unix- og Windows-spesifikke deler separat.

I tillegg til tekst- og filbehandling er tids- og datomanipulering ofte relevant. Biblioteket chrono tilbyr robuste typer og metoder for å håndtere datoer og tider, med støtte for tidssoner, formatering, og parsing. Det gir programmer muligheten til å operere presist med tidsdata, som ofte er kritisk i logging, rapportering eller tidsavhengige prosesser.

Det er viktig å ha en forståelse for hvordan iteratorer og closures brukes i Rust for å skrive effektiv og uttrykksfull kode. Closure-funksjoner gjør det mulig å kapsle logikk som kan brukes flere steder, for eksempel i filtrering eller transformasjoner av data. Iterator-metoder som tar closures som argument gjør koden svært fleksibel og kompakt.

Komposisjon av kommandoer og verktøy, slik som muligheten til å kombinere flere operasjoner kjedet sammen, er en sentral idé i Unix-filosofien og gjenspeiles i hvordan Rust-programmer kan bygges for å være modulære og sammensatte. Exit-koder spiller også en rolle her, da de kommuniserer suksess eller feilstatus tilbake til systemet, noe som er viktig i skript og automatisering.

Når man arbeider med tekstformater som CSV, er det essensielt å forstå hvordan felt kan parses og skrives ut. Biblioteket csv gir et rikholdig API for å lese, skrive og manipulere kommaseparerte filer, som er et av de mest brukte formatene for datautveksling.

Til slutt er det verdt å merke seg at en god forståelse av grunnleggende kommandolinjeverktøy og deres implementasjon i Rust gir et solid fundament for å bygge egne robuste og effektive CLI-programmer. Forståelsen av hvordan ulike elementer som argumenthåndtering, filoperasjoner, tekstmanipulering, tidsbehandling og testing spiller sammen, er nøkkelen til å mestre utvikling i dette området.

Viktig er også å anerkjenne kompleksiteten som kan oppstå ved internasjonalisering, spesielt når man arbeider med Unicode og forskjellen mellom tegn og bytes, og hvordan dette påvirker operasjoner som slicing og telling. Evnen til å skrive kode som håndterer disse nyansene korrekt gjør programmer mer robuste og brukervennlige i et globalt miljø.