Rust ile CLI işlemleri

Veli Uysal
Turkiye Rust Community
6 min readJun 25, 2024

--

Daha önceki yazılarımda temel düzeyde Rust diline giriş yapmıştım. Bu yazımla birlikte bir CLI dediğimiz komut satırından verileri alıp gerekli aksiyonları nasıl yapabildiğimize bakacağız. Öncelikle CLI nedir ile başlayıp devamında Rust dilinde kullanabileceğimiz bazı CLI crate’lerinden bahsedip onlarla yapabileceklerimizi inceleyeceğiz. Haydi CLI’lanalım.

CLI Nedir?

CLI” kısaltması “Command-Line Interface” ifadesinin baş harflerinden oluşur. CLI, bilgisayar yazılımlarının veya işletim sistemlerinin komut satırı arayüzü anlamına gelir. Kullanıcıların metin tabanlı komutlar girerek bilgisayar kaynaklarını yönetmelerine veya programları çalıştırmalarına olanak tanır. CLI, genellikle teknik kullanıcılar veya geliştiriciler tarafından tercih edilir, çünkü doğrudan ve detaylı kontrol sağlar. Örneğin, Windows’da Command Prompt veya PowerShell, Linux ve Unix sistemlerinde Terminal gibi uygulamalar birer CLI örnekleridir. CLI’nin temel özellikleri ve kullanım alanları:

  • Komutlar ve Parametreler: CLI kullanıcıları, komutları girerek ve isteğe bağlı parametreler ekleyerek sistemde belirli işlemleri gerçekleştirebilirler. Örneğin, dosya yönetimi için ls (list), cd (change directory), mkdir (make directory) gibi komutlar kullanılabilir. Her komut genellikle belirli bir sözdizimine ve seçeneklere sahiptir.
  • Yönlendirme ve Boru Hattı İşlemleri: CLI’de, komut çıktıları başka komutlara göre veya “boru hattı” (|) işaretiyle (pipe) birleştirilebilir. Bu sayede, bir komutun çıktısını doğrudan başka bir komutun girişi olarak kullanabilirsiniz. Örneğin, Linux veya Unix sistemlerinde grep komutu ile bir dosyanın içinde belirli bir metni arayabilir ve bu arama sonucunu sort komutuyla sıralayabilirsiniz.
  • Scripting ve Otomasyon: CLI, otomasyon için çok güçlü bir araçtır. Komutlar ve komut dosyaları (script) aracılığıyla, tekrarlayan işlemleri veya karmaşık iş akışlarını otomatikleştirebilirsiniz. Bu, özellikle sistem yöneticileri ve geliştiriciler için büyük bir avantaj sağlar.
  • Hız ve Verimlilik: CLI’nin en önemli avantajlarından biri hız ve verimliliktir. Grafik arayüzlere kıyasla daha hızlı çalışabilir ve daha az sistem kaynağı tüketir. Ayrıca, uzman kullanıcılar için direkt ve detaylı kontrol sağladığından tercih edilir.
  • Çapraz Platform Destek: CLI, genellikle işletim sistemlerinden bağımsızdır ve çoğu modern işletim sisteminde kullanılabilir. Örneğin, Windows, macOS ve çeşitli Linux dağıtımlarında CLI kullanımı mümkündür.

Rust Dilinde CLI İşlemleri

Rust dili genel amaçlı bir programlama dilidir ve güçlü bir sistem programlama dilidir. CLI uygulamaları ile Rust dilinde genellikle kullanıcıların terminal üzerinden komutlar verip işlemler yapmasına olanak tanınmaktadır. Rust dilinde CLI geliştirme için yaygın olarak kullanılan bazı kütüphaneler ve araçlar şunlardır:

i) std::env modülü: Rust’ın standart kütüphanesinde bulunan std::env modülü, ortam değişkenlerini yönetmek ve programınızın çalıştığı ortamı kontrol etmek için kullanılır. Bu da CLI uygulamalarında kullanışlıdır. Bu modülün temel işlevleri aşağıdaki gibidir:

  • Başlatılma esnasında parametre almak: args() fonksiyonu, Rust programının komut satırı argümanlarını almak için kullanılır. Bu fonksiyon bize programın başlatılması sırasında verilen argümanları bir vektör halinde döndürür.
use std::env;

fn main() {
let args: Vec<String> = env::args().collect();
println!("{:?}", args);
}
  • Çalışma Ortamı Değişkenleri: var() ve var_os() fonksiyonları, programın çalıştığı işletim sistemi ortam değişkenlerine erişim sağlar. var() fonksiyonu, bir ortam değişkeninin değerini String olarak döndürürken, var_os() fonksiyonu ise OsString türünde döndürür.
use std::env;

fn main() {
let path = env::var("PATH").unwrap_or_else(|_| "/usr/bin".to_string());
println!("PATH: {}", path);
}
  • Geçici Ortam Değişkenleri: set_var() fonksiyonu, geçici olarak bir ortam değişkeni ayarlamak için kullanılır.
use std::env;

fn main() {
env::set_var("MY_VAR", "value");
println!("MY_VAR: {}", env::var("MY_VAR").unwrap());
}
  • Çalışma Dizinini Alma: current_dir() fonksiyonu, programın çalıştığı mevcut çalışma dizinini döndürür.
use std::env;

fn main() {
let current_dir = env::current_dir().unwrap();
println!("Current directory: {}", current_dir.display());
}

Bu temel fonksiyonlar sayesinde std::env modülü, Rust programlarının çalıştığı ortamı kontrol etmek, ortam değişkenleriyle etkileşim sağlamak ve komut satırı argümanlarını işlemek için gereken araçları sağlar.

ii) std::process modülü: std::process Rust'ın standart kütüphanesinde bulunan ve harici programları çalıştırmak, işletim sistemi süreçlerini yönetmek ve bu süreçlerle etkileşim sağlamak için kullanılan fonksiyonları içermektedir. Rust dilinde kodlama yapanlara güçlü ve taşınabilir bir şekilde işletim sistemi düzeyinde işlemler yapma imkanını sağlar. Bu modülün temel işlevleri aşağıdaki gibidir:

  • Harici Program Çalıştırma: Bir komut çalıştırabilmek için Command struct'ı ve onun yöntemleri (spawn(), output(), status()) aracılığıyla harici programları çalıştırabilmenizi sağlar. Aşağıdaki örnekteki gibi bir kullanıma sahiptir.
use std::process::Command;

fn main() {
let output = Command::new("ls")
.arg("-l")
.output()
.expect("Failed to execute command");

println!("Output:\n{}", String::from_utf8_lossy(&output.stdout));
}
  • Harici Program Çalıştırma ve Bekleme İşlemleri: Bir çok programlama dilinde de aynı isimlendirmeye sahip spawn() fonksiyonu, bir süreci başlatır ve bu sürecin kontrolünü sağlar. wait() veya wait_with_output() fonksiyonları, sürecin tamamlanmasını bekler ve sonucunu döndürür. Aşağıdaki örnekteki gibi bir kullanıma sahiptir.
use std::process::{Command, Stdio};

fn main() {
let mut child = Command::new("echo")
.arg("Hello, Rust!")
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start command");

let output = child.wait_with_output().expect("Failed to wait on child");

println!("Output: {}", String::from_utf8_lossy(&output.stdout));
}
  • Süreç Durumu Kontrolünün Yapılması: Child struct'ı, bir sürecin kontrolünü sağlar ve wait(), kill() gibi yöntemler aracılığıyla süreçle etkileşim sağlar. Aşağıdaki örnekteki gibi bir kullanıma sahiptir.
use std::process::{Command, Child};

fn main() {
let mut child: Child = Command::new("sleep")
.arg("3")
.spawn()
.expect("Failed to start command");

match child.wait() {
Ok(status) => println!("Child process exited with status: {}", status),
Err(e) => eprintln!("Error waiting for child process: {}", e),
}
}
  • Girdi ve Çıktı Yönlendirme Fonksiyonları: stdin(), stdout(), stderr() yöntemleri aracılığıyla süreçlerin standart giriş, çıkış ve hata akımlarını yönlendirebilirsiniz. Aşağıdaki örnekteki gibi kullanları bulunmaktadır.
use std::process::{Command, Stdio};

fn main() {
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start command");

child.stdin
.as_mut()
.expect("Failed to open stdin")
.write_all(b"Hello, Rust!\n")
.expect("Failed to write to stdin");

let output = child.wait_with_output().expect("Failed to wait on child");

println!("Output: {}", String::from_utf8_lossy(&output.stdout));
}

iii) clap crate’i: Rust dilinde clap (Command Line Argument Parser) crate’i komut satırı argümanlarını ayrıştırmak için kullanılan popüler ve güçlü bir kütüphanedir. clap crate’i kullanıcı dostu arayüzler oluşturmayı, hata mesajlarını yönetmeyi ve argümanları doğrulamayı kolaylaştıran bir yapıya sahiptir. clap ile yazılan CLI uygulamaları, kullanıcılara net ve anlaşılır yardım mesajları sunmaktadır. Benimde yazdığım CLI uygulamalarında kullandığım bir crate’dir. Bu crate’in temel işlevleri aşağıdaki gibidir:

  • command fonksiyonları: Command modülü yardımıyla CLI uygulamamızı tanımlamak için kullanmaktayız. Bu fonksiyonlar yardımıyla komutların ve argümanların nasıl yapılandırılacağını belirleyebiliriz. Bu fonksiyonların isimleri ve kullanım amaçları aşağıdaki gibidir.
new: Yeni bir Command oluşturur.
version: Uygulamanın versiyon bilgisini belirtir.
author: Uygulamanın yazar bilgisini belirtir.
about: Uygulamanın kısa açıklamasını belirtir.
arg: Komuta bir argüman ekler.
subcommand: Komuta bir alt komut ekler.
get_matches: Komut satırı argümanlarını ayrıştırır ve eşleştirir.
  • Arg fonksiyonları: Arg modülü içerisinde bulunan fonskiyonlar komut satırı argümanlarını tanımlamak için kullanılmaktadır. Bu fonksiyonların isimleri ve kullanım amaçları aşağıdaki gibidir.
new: Yeni bir Arg oluşturur.
short: Kısa bayrak tanımlar.
long: Uzun bayrak tanımlar.
value_name: Argümanın değer adını belirtir.
help: Argümanın yardım mesajını belirtir.
takes_value: Argümanın bir değer alacağını belirtir.
multiple_occurrences: Argümanın birden fazla kez kullanılabileceğini belirtir.
required: Argümanın zorunlu olduğunu belirtir.
  • ArgMatches fonksiyonları: ArgMatches fonksiyonları ayrıştırılmış ve eşleştirilmiş komut satırı argümanlarını barındırmaktadır. Bu fonksiyonların isimleri ve kullanım amaçları aşağıdaki gibidir.
value_of: Belirtilen argümanın değerini döner.
is_present: Belirtilen argümanın bulunup bulunmadığını kontrol eder.
occurrences_of: Belirtilen argümanın tekrar sayısını döner.
subcommand_matches: Belirtilen alt komutun eşleşmesini döner.

Clap crate’iyle yapabileceklerimize bir kod örneği vermek istersek aşağıdaki gibi bir örnek verilebilir.

use clap::{Arg, Command};

fn main() {
let matches = Command::new("RustClap")
.version("1.0")
.author("Veli Uysal <veli@turkiyerust.org>")
.about("Türkiye Rust Community")
.arg(
Arg::new("config")
.short('c')
.long("config")
.value_name("FILE")
.help("Sets a custom config file")
.takes_value(true),
)
.arg(
Arg::new("verbose")
.short('v')
.multiple_occurrences(true)
.help("Sets the level of verbosity"),
)
.get_matches();

if let Some(config) = matches.value_of("config") {
println!("Using config file: {}", config);
}

match matches.occurrences_of("verbose") {
0 => println!("No verbose info"),
1 => println!("Some verbose info"),
2 => println!("Tons of verbose info"),
_ => println!("Don't be crazy"),
}
}

iv) structopt crate’i: structopt crate'i, Rust dilinde komut satırı argümanlarını ayrıştırmayı ve yönetmeyi kolaylaştırmayı amaçlayan bir crate’dir. structopt crate’i clap crate'i üzerine inşa edilmiştir ve komut satırı argümanlarını Rust yapılarına (struct) otomatik olarak dönüştürmeyi sağlamaktadır. Bu otomatik dönüştürme sayesinde kodun daha okunabilir ve yönetilebilir olması sağlanmaktadır. structopt crate’i ile komut satırı arayüzlerini oluştururken Rust'ın güçlü tip sistemini ve makro yetenekleri kullanılmaktadır. Bu crate’in temel özellikleri ise şöyledir:

  • Basit ve Okunaklı: structopt, komut satırı argümanlarını ayrıştırmak için yapı tanımları kullanır. Bu, kodun okunabilirliğini artırır ve argümanların yönetimini kolaylaştırır.
  • Güçlü Tip Sistemi: Rust’ın tip sistemini kullanarak, argümanların türlerini otomatik olarak denetler ve dönüştürür.
  • Derive Makroları: #[derive(StructOpt)] makrosu ile yapıların argümanları nasıl ayrıştıracağını ve doğrulayacağını otomatik olarak belirler.
  • Kompleks Yapı Desteği: Alt komutlar, opsiyonel argümanlar ve bayraklar gibi kompleks komut satırı arayüzlerini destekler.

Sosyal medya hesaplarım: Twitter | Linkedin | Github | Youtube

--

--