CodeWithAbdessamad

Cli Tools

Real Projects: CLI Tools

In this chapter, we dive into practical C programming by building real-world command-line tools. These projects bridge theory and practice, helping you understand how to create efficient, user-friendly applications that solve everyday problems without GUI overhead. Let’s start with two foundational tools: a File Manager and a Text Processor.


File Manager

A CLI file manager is essential for navigating your system efficiently. Unlike GUI tools, it’s lightweight, fast, and integrates seamlessly with your shell workflow. We’ll build a minimal but functional file manager that lists directory contents and handles directory changes—perfect for environments where speed and resource efficiency matter.

Core Concepts

Before coding, understand these key C concepts:

  • Directory navigation: Using chdir() from to switch directories
  • File listing: Iterating through directory entries with opendir(), readdir(), and closeset()
  • Error handling: Checking return values from system calls (critical for robust CLI tools)

Building a Minimal File Manager

Here’s a step-by-step implementation of a CLI file manager with two commands: ls (list files) and cd (change directory). This tool avoids complex features (like file operations) to focus on core navigation.

<code class="language-c">#include <stdio.h>
<p>#include <stdlib.h></p>
<p>#include <dirent.h></p>
<p>#include <string.h></p>

<p>// Helper function to print directory entries</p>
<p>void print<em>dir</em>entries(DIR *dir) {</p>
<p>    struct dirent *entry;</p>
<p>    while ((entry = readdir(dir)) != NULL) {</p>
<p>        if (strcmp(entry->d<em>name, ".") != 0 && strcmp(entry->d</em>name, "..") != 0) {</p>
<p>            printf("%s\n", entry->d_name);</p>
<p>        }</p>
<p>    }</p>
<p>}</p>

<p>int main(int argc, char *argv[]) {</p>
<p>    if (argc < 2) {</p>
<p>        printf("Usage: file_manager ls [directory] | cd [directory]\n");</p>
<p>        return 1;</p>
<p>    }</p>

<p>    if (strcmp(argv[1], "ls") == 0) {</p>
<p>        // List current directory by default</p>
<p>        DIR *dir = opendir(".");</p>
<p>        if (dir == NULL) {</p>
<p>            perror("opendir");</p>
<p>            return 1;</p>
<p>        }</p>
<p>        print<em>dir</em>entries(dir);</p>
<p>        closedir(dir);</p>
<p>    } else if (strcmp(argv[1], "cd") == 0) {</p>
<p>        // Change directory</p>
<p>        char *path = argv[2];</p>
<p>        if (chdir(path) != 0) {</p>
<p>            perror("chdir");</p>
<p>            return 1;</p>
<p>        }</p>
<p>        printf("Changed directory to: %s\n", path);</p>
<p>    } else {</p>
<p>        printf("Unknown command. Use 'ls' or 'cd'.\n");</p>
<p>        return 1;</p>
<p>    }</p>
<p>    return 0;</p>
<p>}</code>

Why this works:

  • The ls command lists all files (excluding . and ..).
  • The cd command changes the current working directory using chdir().
  • Error handling ensures the tool doesn’t crash unexpectedly (e.g., invalid paths).

Real-World Usage

Run this in your terminal:

<code class="language-bash"># List current directory
<p>file_manager ls</p>

<h1>Change to a subdirectory</h1>
<p>file_manager cd ./docs</code>

Pro Tip: Add stat() calls to show file sizes or dates for richer output. This is a great starting point for expanding into full-featured file managers.


Text Processor

Text processors handle text manipulation—perfect for tasks like log analysis, data cleaning, or quick file transformations. We’ll build a lightweight tool that replaces text patterns and counts words in files, demonstrating how C excels at low-level text operations.

Core Concepts

  • File reading: fopen(), fread(), and fgetc() for line-by-line processing
  • String manipulation: strcat(), strlen(), and strstr() for pattern matching
  • Command-line arguments: Parsing flags (e.g., -s for search, -r for replace)

Building a Text Processor

Here’s a minimal text processor with two commands: replace (find-and-replace) and wordcount (count words). This tool focuses on clarity and practical utility.

<code class="language-c">#include <stdio.h>
<p>#include <stdlib.h></p>
<p>#include <string.h></p>

<p>// Helper function to count words in a file</p>
<p>int count_words(FILE *file) {</p>
<p>    int word_count = 0;</p>
<p>    int in_word = 0;</p>
<p>    int c;</p>
<p>    </p>
<p>    while ((c = fgetc(file)) != EOF) {</p>
<p>        if (c == ' ' || c == '\n' || c == '\t') {</p>
<p>            if (in_word) {</p>
<p>                word_count++;</p>
<p>                in_word = 0;</p>
<p>            }</p>
<p>        } else {</p>
<p>            if (!in_word) {</p>
<p>                in_word = 1;</p>
<p>            }</p>
<p>        }</p>
<p>    }</p>
<p>    return word_count;</p>
<p>}</p>

<p>// Helper function to replace text</p>
<p>void replace_text(FILE <em>file, const char </em>search, const char *replace) {</p>
<p>    char buffer[1024];</p>
<p>    size_t len;</p>
<p>    while (fgets(buffer, sizeof(buffer), file) != NULL) {</p>
<p>        len = strlen(buffer);</p>
<p>        // Replace all occurrences (simplified for demo)</p>
<p>        char *pos = strstr(buffer, search);</p>
<p>        while (pos) {</p>
<p>            size<em>t replace</em>len = strlen(replace);</p>
<p>            size<em>t search</em>len = strlen(search);</p>
<p>            char *new<em>buffer = malloc(len - search</em>len + replace_len + 1);</p>
<p>            if (new_buffer) {</p>
<p>                strncpy(new_buffer, buffer, pos - buffer);</p>
<p>                strncat(new<em>buffer, replace, replace</em>len);</p>
<p>                strncat(new<em>buffer, buffer + pos + search</em>len, len - (pos - buffer) - search_len);</p>
<p>                new<em>buffer[len - search</em>len + replace_len] = '\0';</p>
<p>                // Write new_buffer back to file (simplified)</p>
<p>                printf("Replaced: %s -> %s\n", search, replace);</p>
<p>                // In real use: write new_buffer to file</p>
<p>                free(new_buffer);</p>
<p>            }</p>
<p>            pos = strstr(pos + search_len, search);</p>
<p>        }</p>
<p>    }</p>
<p>}</p>

<p>int main(int argc, char *argv[]) {</p>
<p>    if (argc < 3) {</p>
<p>        printf("Usage: text_processor replace [search] [replace] | wordcount [file]\n");</p>
<p>        return 1;</p>
<p>    }</p>

<p>    if (strcmp(argv[1], "replace") == 0) {</p>
<p>        FILE *file = fopen(argv[2], "r");</p>
<p>        if (!file) {</p>
<p>            perror("fopen");</p>
<p>            return 1;</p>
<p>        }</p>
<p>        replace_text(file, argv[3], argv[4]);</p>
<p>        fclose(file);</p>
<p>    } else if (strcmp(argv[1], "wordcount") == 0) {</p>
<p>        FILE *file = fopen(argv[2], "r");</p>
<p>        if (!file) {</p>
<p>            perror("fopen");</p>
<p>            return 1;</p>
<p>        }</p>
<p>        int count = count_words(file);</p>
<p>        printf("Word count: %d\n", count);</p>
<p>        fclose(file);</p>
<p>    } else {</p>
<p>        printf("Unknown command. Use 'replace' or 'wordcount'.\n");</p>
<p>        return 1;</p>
<p>    }</p>
<p>    return 0;</p>
<p>}</code>

Why this works:

  • wordcount uses a state machine to count words without regex (efficient for C).
  • replace_text finds and replaces patterns line-by-line (ideal for small files).
  • The tool handles errors gracefully (e.g., missing files).

Real-World Usage

Run these in your terminal:

<code class="language-bash"># Count words in a log file
<p>text_processor wordcount logs.txt</p>

<h1>Replace "error" with "issue" in a file</h1>
<p>text_processor replace "error" "issue" data.log</code>

Pro Tip: For production use, add file buffering and error logging. This foundation scales to handle larger files with minimal overhead.


Summary

In this chapter, we built two practical CLI tools that showcase C’s strengths in system-level programming: a File Manager for efficient directory navigation and a Text Processor for text manipulation tasks. Both projects emphasize error handling, resource efficiency, and real-world usability—key principles for professional CLI development. These tools are a great starting point for expanding into more complex applications while maintaining C’s performance advantages. 💡