Çevre Değişkenleri ile Çalışma
minigrep ikili dosyasına ek bir özellik kazandırarak programımızı geliştireceğiz: kullanıcının bir çevre değişkeni (environment variable) aracılığıyla etkinleştirebileceği büyük/küçük harf duyarsız arama seçeneği. Bu özelliği bir komut satırı seçeneği olarak da tasarlayabilir ve kullanıcıların ihtiyaç duydukları her seferinde bunu girmesini zorunlu tutabilirdik; ancak bunun yerine bir çevre değişkeni kullanarak kullanıcılarımızın değişkeni yalnızca bir kez tanımlamasını ve o terminal oturumu boyunca yapacakları tüm aramaların büyük/küçük harfe duyarsız çalışmasını sağlıyoruz.
Büyük/Küçük Harf Duyarsız Arama İçin Başarısız Olan Bir Test Yazma
İlk olarak minigrep kütüphanesine, çevre değişkenine bir değer atandığında çağrılacak buyuk_kucuk_harf_duyarsiz_ara adlı yeni bir fonksiyon ekleyeceğiz. TDD sürecini izlemeye devam edeceğimiz için ilk adımımız yine başarısız olan bir test yazmak olacak. Liste 12-20’de gösterildiği gibi yeni buyuk_kucuk_harf_duyarsiz_ara fonksiyonu için yeni bir test ekleyecek ve iki test arasındaki farkı netleştirmek amacıyla eski testimizin adını tek_sonuc yerine buyuk_kucuk_harf_duyarli olarak değiştireceğiz.
pub fn ara<'a>(sorgu: &str, icerik: &'a str) -> Vec<&'a str> {
let mut sonuclar = Vec::new();
for satir in icerik.lines() {
if satir.contains(sorgu) {
sonuclar.push(satir);
}
}
sonuclar
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buyuk_kucuk_harf_duyarli() {
let sorgu = "güven";
let icerik = "\
Güven:
güvenli, hızlı, üretken.
Üçünü de seç.
Güven kolay kazanılmaz.";
assert_eq!(vec!["güvenli, hızlı, üretken."], ara(sorgu, icerik));
}
#[test]
fn buyuk_kucuk_harf_duyarsiz() {
let sorgu = "gÜvEn";
let icerik = "\
Güven:
önce sağlamlık gelir.
Planlı çalış, sakin ol.
güven kazanılır.";
assert_eq!(
vec!["Güven:", "güven kazanılır."],
buyuk_kucuk_harf_duyarsiz_ara(sorgu, icerik)
);
}
}
Eski testin icerik metnini de düzenlediğimize dikkat edin. Büyük/küçük harfe duyarlı arama yaptığımızda "güven" sorgusuyla eşleşmemesi gereken, büyük G harfiyle başlayan "Güven kolay kazanılmaz." satırını ekledik. Eski testi bu şekilde güncellemek, daha önce geliştirdiğimiz büyük/küçük harf duyarlı arama işlevselliğini yanlışlıkla bozmadığımızdan emin olmamıza yardımcı olur. Bu test şimdi geçmeli ve biz büyük/küçük harf duyarsız arama üzerinde çalışırken de geçmeye devam etmelidir.
Büyük/küçük harfe duyarsız arama için yazdığımız yeni test ise sorgu olarak "gÜvEn" ifadesini kullanıyor. Eklemek üzere olduğumuz buyuk_kucuk_harf_duyarsiz_ara fonksiyonunda "gÜvEn" sorgusu, her ikisi de sorgudan farklı büyük/küçük harf kullanımına sahip olsa bile büyük G harfi içeren "Güven:" satırıyla ve "güven kazanılır." satırıyla eşleşmelidir. Bu bizim başarısız testimizdir ve henüz buyuk_kucuk_harf_duyarsiz_ara fonksiyonunu tanımlamadığımız için derlenmeyecektir. Testin derlenip başarısız olduğunu görmek isterseniz, Liste 12-16’da ara fonksiyonu için yaptığımıza benzer biçimde her zaman boş bir vektör döndüren iskelet bir uygulama ekleyebilirsiniz.
buyuk_kucuk_harf_duyarsiz_ara Fonksiyonunu Uygulama
Liste 12-21’de gösterilen buyuk_kucuk_harf_duyarsiz_ara fonksiyonu, ara fonksiyonuyla neredeyse aynı olacaktır. Tek fark şudur: sorgu’yu ve her bir satir’ı küçük harfe dönüştüreceğiz; böylece girdi argümanları hangi büyük/küçük harf diziliminde olursa olsun, satırın sorguyu içerip içermediğini kontrol ettiğimiz anda her ikisi de küçük harfli olacaktır.
pub fn ara<'a>(sorgu: &str, icerik: &'a str) -> Vec<&'a str> {
let mut sonuclar = Vec::new();
for satir in icerik.lines() {
if satir.contains(sorgu) {
sonuclar.push(satir);
}
}
sonuclar
}
pub fn buyuk_kucuk_harf_duyarsiz_ara<'a>(
sorgu: &str,
icerik: &'a str,
) -> Vec<&'a str> {
let sorgu = sorgu.to_lowercase();
let mut sonuclar = Vec::new();
for satir in icerik.lines() {
if satir.to_lowercase().contains(&sorgu) {
sonuclar.push(satir);
}
}
sonuclar
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn buyuk_kucuk_harf_duyarli() {
let sorgu = "güven";
let icerik = "\
Güven:
güvenli, hızlı, üretken.
Üçünü de seç.
Güven kolay kazanılmaz.";
assert_eq!(vec!["güvenli, hızlı, üretken."], ara(sorgu, icerik));
}
#[test]
fn buyuk_kucuk_harf_duyarsiz() {
let sorgu = "gÜvEn";
let icerik = "\
Güven:
önce sağlamlık gelir.
Planlı çalış, sakin ol.
güven kazanılır.";
assert_eq!(
vec!["Güven:", "güven kazanılır."],
buyuk_kucuk_harf_duyarsiz_ara(sorgu, icerik)
);
}
}
buyuk_kucuk_harf_duyarsiz_ara fonksiyonunu tanımlamaİlk olarak sorgu string’ini küçük harfe dönüştürüyor ve orijinal sorgu değişkenini gölgeleyerek aynı adlı yeni bir değişkende saklıyoruz. Sorgu üzerinde to_lowercase metodunu çağırmamız gereklidir; böylece kullanıcının sorgusu "güven", "GÜVEN", "Güven" veya "gÜvEn" değerlerinden hangisi olursa olsun sorguyu sanki "güven" girilmiş gibi ele alır ve büyük/küçük harf farkını yok sayarız. to_lowercase temel Unicode dönüşümlerini işleyebilse de yüzde 100 isabetli değildir. Gerçek bir uygulama geliştiriyor olsaydık burada biraz daha kapsamlı bir işlem yapmamız gerekirdi; ancak bu bölümün konusu Unicode değil çevre değişkenleri olduğundan şimdilik bunu bu şekilde bırakacağız.
sorgu değişkeninin artık bir string dilimi (&str) değil, bir String olduğuna dikkat edin; çünkü to_lowercase çağrısı mevcut veriye referans vermek yerine yeni veri oluşturur. Örneğin sorgunun "gÜvEn" olduğunu varsayalım: Bu string dilimi doğrudan ödünç alabileceğimiz küçük harfli bir ü veya e barındırmaz; bu nedenle öbekte "güven" metnini tutan yeni bir String tahsis etmemiz gerekir. Artık sorgu değişkenini contains metoduna argüman olarak aktarırken başına & işareti eklememiz gerekir, çünkü contains metodunun imzası bir string dilimi alacak şekilde tanımlanmıştır.
Ardından tüm karakterleri küçük harfe dönüştürmek için her satir üzerinde de bir to_lowercase çağrısı ekliyoruz. Artık hem satir hem de sorgu küçük harfe dönüştürüldüğüne göre, sorgunun büyük/küçük harf yazımı ne olursa olsun eşleşmeleri bulacağız.
Not: Burada hem bellek yönetimi hem de Türkçe metin işleme açısından dikkat çekici iki incelik vardır:
- Bellek Tahsisi ve Dilim Ömrü:
sorgu.to_lowercase()çağrısınıfordöngüsünün dışında yalnızca bir kez yaparak her satır için sorguyu tekrar tekrar öbekte tahsis etmekten kaçınıyoruz. Döngü içindesatir.to_lowercase()karşılaştırma amacıyla geçici birStringüretip satır sonunda düşürülse de, eşleşme sağlandığındasonuclar.push(satir)ifadesiyle bu geçici kopyayı değil, doğrudanicerikiçindeki orijinal&'a strdilimini döndürüyoruz; böylece sonuçlarda satırın özgün büyük/küçük harf yazımı korunur. - Türkçede
'İ've'I'Harflerinin Unicode Davranışı: Standart kütüphanedekistr::to_lowercasemetodu yerel ayardan (locale) bağımsız genel Unicode kurallarını uygular. Bu nedenle Türkçe büyük noktalı'İ'(U+0130, 2 bayt) üzerinde çağrıldığında tek bir'i'yerine'i'harfi ve birleştirici üst nokta karakteri üretir ("i\u{307}", ikicharve 3 bayt — bu durumto_lowercasemetodunun neden aynı bayt uzunluğunu koruyamayıp yeni birStringdöndürmek zorunda olduğunun da somut bir örneğidir). Benzer şekilde noktasız'I'harfini Türkçe'ı'yerine İngilizce kurallarıyla'i'harfine dönüştürür. Sonuç olarak"İSTANBUL".to_lowercase().contains("istanbul")ve"Işık".to_lowercase().contains("ışık")ifadelerifalsedöndürür! Gerçek dünya Türkçe metin arama uygulamalarında karşılaştırma öncesinde'İ' -> 'i've'I' -> 'ı'dönüşümünü açıkça uygulamak veya ICU4X ekosistemindekiicu_casemapgibi yerel ayar duyarlı (locale-aware) crate’lerden yararlanmak gerekir.
Bakalım bu uygulama testleri geçecek mi:
$ cargo test
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.33s
Running unittests src/lib.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 2 tests
test tests::buyuk_kucuk_harf_duyarsiz ... ok
test tests::buyuk_kucuk_harf_duyarli ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/minigrep-9cd200e5fac0fc94)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests minigrep
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Harika! Testler geçti. Şimdi yeni buyuk_kucuk_harf_duyarsiz_ara fonksiyonunu calistir fonksiyonunun içinden çağıralım. İlk olarak Yapilandirma struct’ına büyük/küçük harfe duyarlı ve duyarsız arama arasında geçiş yapacak bir yapılandırma seçeneği ekleyeceğiz. Bu alanı henüz hiçbir yerde ilklendirmediğimiz için şu aşamada derleyici hatası alacağız:
Dosya adı: src/main.rs
use std::env;
use std::error::Error;
use std::fs;
use std::process;
use minigrep::{ara, buyuk_kucuk_harf_duyarsiz_ara};
// --snip--
fn main() {
let argumanlar: Vec<String> = env::args().collect();
let yapilandirma =
Yapilandirma::olustur(&argumanlar).unwrap_or_else(|hata| {
println!("Argümanları ayrıştırırken problem oluştu: {hata}");
process::exit(1);
});
if let Err(e) = calistir(yapilandirma) {
println!("Uygulama hatası: {e}");
process::exit(1);
}
}
pub struct Yapilandirma {
pub sorgu: String,
pub dosya_yolu: String,
pub buyuk_kucuk_harf_yoksay: bool,
}
impl Yapilandirma {
fn olustur(argumanlar: &[String]) -> Result<Yapilandirma, &'static str> {
if argumanlar.len() < 3 {
return Err("yeterli argüman yok");
}
let sorgu = argumanlar[1].clone();
let dosya_yolu = argumanlar[2].clone();
Ok(Yapilandirma { sorgu, dosya_yolu })
}
}
fn calistir(yapilandirma: Yapilandirma) -> Result<(), Box<dyn Error>> {
let icerik = fs::read_to_string(yapilandirma.dosya_yolu)?;
let sonuclar = if yapilandirma.buyuk_kucuk_harf_yoksay {
buyuk_kucuk_harf_duyarsiz_ara(&yapilandirma.sorgu, &icerik)
} else {
ara(&yapilandirma.sorgu, &icerik)
};
for satir in sonuclar {
println!("{satir}");
}
Ok(())
}
Mantıksal (bool) bir değer tutan buyuk_kucuk_harf_yoksay alanını ekledik. Ardından Liste 12-22’de gösterildiği gibi calistir fonksiyonunun buyuk_kucuk_harf_yoksay alanının değerini kontrol etmesini ve buna göre ara fonksiyonunu mu yoksa buyuk_kucuk_harf_duyarsiz_ara fonksiyonunu mu çağıracağına karar vermesini sağlamamız gerekiyor. Bu kod henüz derlenmeyecektir.
use std::env;
use std::error::Error;
use std::fs;
use std::process;
use minigrep::{ara, buyuk_kucuk_harf_duyarsiz_ara};
// --snip--
fn main() {
let argumanlar: Vec<String> = env::args().collect();
let yapilandirma =
Yapilandirma::olustur(&argumanlar).unwrap_or_else(|hata| {
println!("Argümanları ayrıştırırken problem oluştu: {hata}");
process::exit(1);
});
if let Err(e) = calistir(yapilandirma) {
println!("Uygulama hatası: {e}");
process::exit(1);
}
}
pub struct Yapilandirma {
pub sorgu: String,
pub dosya_yolu: String,
pub buyuk_kucuk_harf_yoksay: bool,
}
impl Yapilandirma {
fn olustur(argumanlar: &[String]) -> Result<Yapilandirma, &'static str> {
if argumanlar.len() < 3 {
return Err("yeterli argüman yok");
}
let sorgu = argumanlar[1].clone();
let dosya_yolu = argumanlar[2].clone();
Ok(Yapilandirma { sorgu, dosya_yolu })
}
}
fn calistir(yapilandirma: Yapilandirma) -> Result<(), Box<dyn Error>> {
let icerik = fs::read_to_string(yapilandirma.dosya_yolu)?;
let sonuclar = if yapilandirma.buyuk_kucuk_harf_yoksay {
buyuk_kucuk_harf_duyarsiz_ara(&yapilandirma.sorgu, &icerik)
} else {
ara(&yapilandirma.sorgu, &icerik)
};
for satir in sonuclar {
println!("{satir}");
}
Ok(())
}
yapilandirma.buyuk_kucuk_harf_yoksay içindeki değere bağlı olarak ara veya buyuk_kucuk_harf_duyarsiz_ara fonksiyonunu çağırmaSon olarak çevre değişkenini kontrol etmemiz gerekiyor. Çevre değişkenleriyle çalışmaya yönelik fonksiyonlar, standart kütüphanenin hâlihazırda src/main.rs dosyasının en üstünde kapsama dahil ettiğimiz env modülünde yer alır. Liste 12-23’te gösterildiği gibi, IGNORE_CASE adlı çevre değişkenine herhangi bir değer atanıp atanmadığını öğrenmek için env modülündeki var fonksiyonunu kullanacağız.
use std::env;
use std::error::Error;
use std::fs;
use std::process;
use minigrep::{ara, buyuk_kucuk_harf_duyarsiz_ara};
fn main() {
let argumanlar: Vec<String> = env::args().collect();
let yapilandirma =
Yapilandirma::olustur(&argumanlar).unwrap_or_else(|hata| {
println!("Argümanları ayrıştırırken problem oluştu: {hata}");
process::exit(1);
});
if let Err(e) = calistir(yapilandirma) {
println!("Uygulama hatası: {e}");
process::exit(1);
}
}
pub struct Yapilandirma {
pub sorgu: String,
pub dosya_yolu: String,
pub buyuk_kucuk_harf_yoksay: bool,
}
impl Yapilandirma {
fn olustur(argumanlar: &[String]) -> Result<Yapilandirma, &'static str> {
if argumanlar.len() < 3 {
return Err("yeterli argüman yok");
}
let sorgu = argumanlar[1].clone();
let dosya_yolu = argumanlar[2].clone();
let buyuk_kucuk_harf_yoksay = env::var("IGNORE_CASE").is_ok();
Ok(Yapilandirma {
sorgu,
dosya_yolu,
buyuk_kucuk_harf_yoksay,
})
}
}
fn calistir(yapilandirma: Yapilandirma) -> Result<(), Box<dyn Error>> {
let icerik = fs::read_to_string(yapilandirma.dosya_yolu)?;
let sonuclar = if yapilandirma.buyuk_kucuk_harf_yoksay {
buyuk_kucuk_harf_duyarsiz_ara(&yapilandirma.sorgu, &icerik)
} else {
ara(&yapilandirma.sorgu, &icerik)
};
for satir in sonuclar {
println!("{satir}");
}
Ok(())
}
IGNORE_CASE adlı çevre değişkeninde herhangi bir değer olup olmadığını kontrol etmeBurada buyuk_kucuk_harf_yoksay adında yeni bir değişken oluşturuyoruz. Değerini belirlemek için env::var fonksiyonunu çağırıyor ve ona IGNORE_CASE çevre değişkeninin adını aktarıyoruz. env::var fonksiyonu bir Result döndürür: Çevre değişkeni herhangi bir değere ayarlanmışsa bu değişkenin değerini barındıran başarılı Ok varyantı, çevre değişkeni tanımlanmamışsa Err varyantı döner.
Çevre değişkeninin tanımlanıp tanımlanmadığını öğrenmek için Result üzerindeki is_ok metodunu kullanıyoruz; değişken tanımlıysa program büyük/küçük harf duyarsız arama yapacaktır. IGNORE_CASE çevre değişkeni hiçbir değere ayarlanmamışsa is_ok metodu false döndürecek ve program büyük/küçük harfe duyarlı arama gerçekleştirecektir. Çevre değişkeninin değeriyle ilgilenmiyoruz; yalnızca tanımlanmış olup olmaması bizim için yeterli olduğundan unwrap, expect ya da Result üzerinde gördüğümüz diğer metotlar yerine is_ok metodunu kullanıyoruz.
buyuk_kucuk_harf_yoksay değişkenindeki değeri Yapilandirma örneğine aktarıyoruz; böylece calistir fonksiyonu Liste 12-22’de uyguladığımız şekilde bu değeri okuyup buyuk_kucuk_harf_duyarsiz_ara veya ara fonksiyonlarından hangisini çağıracağına karar verebilir.
Haydi deneyelim! İlk olarak programımızı çevre değişkenini tanımlamadan ve tamamı küçük harflerden oluşan ne sorgusuyla çalıştıracağız. Büyük/küçük harfe duyarlı aramada yalnızca küçük harfli ne ifadesini barındıran satırla eşleşmesini bekleriz:
$ cargo run -- ne siir.txt
Compiling minigrep v0.1.0 (file:///projects/minigrep)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.0s
Running `target/debug/minigrep ne siir.txt`
Gelme, artık neye yarar?
Görünüşe göre bu kısım sorunsuz çalışmaya devam ediyor! Şimdi programı IGNORE_CASE değişkenini 1 değerine ayarlayarak yine aynı ne sorgusuyla çalıştıralım:
$ IGNORE_CASE=1 cargo run -- ne siir.txt
PowerShell kullanıyorsanız çevre değişkenini tanımlamayı ve programı çalıştırmayı ayrı komutlar hâlinde yazmanız gerekir:
PS> $Env:IGNORE_CASE=1; cargo run -- ne siir.txt
Bu komut IGNORE_CASE değişkeninin mevcut kabuk oturumunuzun geri kalanı boyunca kalıcı olmasını sağlar. Değişkeni Remove-Item cmdlet’i ile kaldırabilirsiniz:
PS> Remove-Item Env:IGNORE_CASE
Not: Kabuklar (shells) arasında çevre değişkeni tanımlama sözdizimi ve değişkenin ömrü farklılık gösterir:
- Bash / Zsh (Linux, macOS, Git Bash): Komutun hemen önüne yazılan
IGNORE_CASE=1 cargo run ...ifadesi değişkeni yalnızca o komut süresince geçici olarak tanımlar; oturum genelinde kalıcı yapmak içinexport IGNORE_CASE=1, silmek içinunset IGNORE_CASEkullanılır. - PowerShell:
$Env:IGNORE_CASE=1ataması mevcut oturum boyunca kalıcıdır; kaldırmak içinRemove-Item Env:IGNORE_CASEçalıştırılır. - Windows Komut İstemi (
cmd.exe):set IGNORE_CASE=1komutuyla oturum boyunca tanımlanır ve ardındancargo run -- ne siir.txtçalıştırılır; değişkeni silmek içinset IGNORE_CASE=kullanılır (=işaretinin çevresinde boşluk bırakmamaya dikkat edin).
Ayrıca kodumuzda değerin içeriğini değil yalnızca varlığını (env::var("IGNORE_CASE").is_ok()) denetlediğimiz için, değişken silinmediği sürece IGNORE_CASE=0 veya IGNORE_CASE=false atamalarının da büyük/küçük harf duyarsız aramayı etkin tutacağını unutmayın.
Artık büyük harf barındırabilecek ne eşleşmelerini, yani Ne ile başlayan dizeleri de elde etmeliyiz:
Ne hasta bekler sabahı,
Ne taze ölüyü mezar.
Ne de şeytan, bir günahı,
Gelme, artık neye yarar?
Mükemmel, Ne içeren satırları da aldık! minigrep programımız artık bir çevre değişkeniyle kontrol edilen büyük/küçük harf duyarsız arama yapabiliyor. Böylece hem komut satırı argümanları hem de çevre değişkenleri kullanılarak belirlenen seçenekleri nasıl yöneteceğinizi öğrenmiş oldunuz.
Bazı programlar aynı yapılandırma için hem argümanlara hem de çevre değişkenlerine izin verir. Bu gibi durumlarda program, ikisinden birinin öncelikli olmasına karar verir. Kendi başınıza yapabileceğiniz ek bir alıştırma olarak, büyük/küçük harf duyarlılığını hem bir komut satırı argümanıyla hem de bir çevre değişkeniyle kontrol etmeyi deneyin. Program biri büyük/küçük harfe duyarlı, diğeri büyük/küçük harfi yok sayacak biçimde ayarlanarak çalıştırıldığında komut satırı argümanının mı yoksa çevre değişkeninin mi öncelikli olması gerektiğine karar verin.
std::env modülü çevre değişkenleriyle çalışmak için daha pek çok kullanışlı özellik barındırır; nelerin mevcut olduğunu görmek için standart kütüphane belgelerine göz atabilirsiniz.