David's Blog

Using Coreutils in Rust

By David Li on 2023-04-20T14:45:32.838Z

Using Coreutils in Rust

Rust is a systems programming language that aims to provide memory safety, concurrency, and performance. It is an excellent choice for building command-line tools and utilities, making it a natural fit for implementing coreutils, the collection of basic file, shell, and text manipulation tools found on Unix-like systems.

In this article, we will explore how to create coreutils in Rust, focusing on a few examples to demonstrate the power and flexibility of the language.

Table of contents

  1. Introduction to coreutils
  2. Setting up a Rust project
  3. Implementing cat
  4. Implementing wc
  5. Implementing ls
  6. Conclusion

Introduction to coreutils

Coreutils is a package that contains many essential Unix command-line utilities, such as ls, cat, rm, cp, and mv. These utilities are fundamental to working with Unix-like systems and help users perform simple tasks like listing directories, concatenating files, and deleting files.

In this article, we will implement three coreutils in Rust:

  • cat: concatenate files and print on the standard output
  • wc: print newline, word, and byte counts for each file
  • ls: list directory contents

Setting up a Rust project

First, we need to set up a new Rust project. If you don’t have Rust installed, you can follow the official installation guide. Once Rust is installed, create a new project using the following command:

cargo new rust_coreutils --bin
cd rust_coreutils

This command creates a new binary Rust project named rust_coreutils. The project structure should look like this:

rust_coreutils
├── Cargo.toml
└── src
    └── main.rs

Implementing cat

Let’s start by implementing the cat utility, which reads files and outputs their content to the console. We will use the std::fs::File and std::io::Read modules from the Rust standard library to read files.

Here is a basic implementation of the cat utility:

// src/main.rs
use std::env;
use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: cat <file>");
        return Ok(());
    }

    let mut file = File::open(&args[1])?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    print!("{}", contents);

    Ok(())
}

This code reads a file specified as a command-line argument and prints its content to the console. You can build and run the program using cargo run -- file.txt.

Implementing wc

Next, let’s implement the wc utility, which counts the number of lines, words, and bytes in a file. We can reuse parts of the cat implementation to read the file and then use iterators to count lines, words, and bytes.

Here’s a simple implementation of the wc utility:

// src/main.rs
use std::env;
use std::fs::File;
use std::io::{self, Read};

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: wc <file>");
        return Ok(());
    }

    let mut file = File::open(&args[1])?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;

    let lines = contents.lines().count();
    let words = contents.split_whitespace().count();
    let bytes = contents.as_bytes().len();

    println!("{} {} {} {}", lines, words, bytes, args[1]);

    Ok(())
}

This code counts the number of lines, words, and bytes in the specified file and prints the results to the console. You can build and run the program using cargo run -- file.txt.

Implementing ls

Finally, let’s implement the ls utility, which lists the contents of a directory. We can use the std::fs::read_dir function from the Rust standard library to read the contents of a directory and the std::path::Path module to work with file paths.

Here’s a basic implementation of the ls utility:

// src/main.rs
use std::env;
use std::fs;
use std::io::{self,Write};
use std::path::Path;

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    let dir = if args.len() < 2 {
        "."
    } else {
        &args[1]
    };

    let entries = fs::read_dir(Path::new(dir))?;

    for entry in entries {
        let entry = entry?;
        let path = entry.path();
        let filename = path.file_name().unwrap();
        println!("{}", filename.to_string_lossy());
    }

    Ok(())
}

This code lists the contents of the specified directory, or the current directory if no directory is specified. You can build and run the program using cargo run -- path/to/directory.

Conclusion

In this article, we showed how to implement three coreutils in Rust: cat, wc, and ls. Rust’s powerful features, such as pattern matching, error handling, and iterators, make it an excellent choice for building command-line utilities like coreutils.

By implementing these utilities, we demonstrated how to work with files and directories, read file content, count lines, words, and bytes, and list directory contents. These examples are just a starting point for building more complex utilities and exploring the rich ecosystem of Rust libraries and tools.

© Copyright 2023 by Astro Tech Blog. Built with ♥ by FriendlyUser. Last updated on 2023-09-21.