Chat
Ask me anything
Ithy Logo

Simulating the 1980s Peter Norton NCD.EXE Utility in C++

A Comprehensive Guide to Creating a Quick Change Directory Tool

command line directory navigation

Key Takeaways

  • Cross-Platform Compatibility: Utilize C++17's filesystem library for seamless directory management across Windows and POSIX systems.
  • User-Friendly Interface: Implement intuitive command-line interactions with support for favorite directories and search functionalities.
  • Error Handling and Robustness: Incorporate comprehensive error checking to ensure reliability and prevent unexpected behavior.

Introduction

In the realm of command-line utilities, Peter Norton's NCD.EXE from the 1980s stands out as a tool that streamlined directory navigation. Simulating this utility in modern C++ offers both a nostalgic homage and a practical tool for developers and power users. This guide delves into creating a C++ application that mirrors NCD.EXE's functionality, enabling quick directory changes with added modern enhancements.

Understanding NCD.EXE

NCD.EXE, or Norton Change Directory, was designed to simplify the process of navigating through complex directory structures in DOS environments. Users could swiftly switch between frequently accessed directories using predefined shortcuts or by specifying directory paths directly. The utility enhanced productivity by reducing the time spent on repetitive directory navigation tasks.

Implementing NCD.EXE in C++

Cross-Platform Directory Management

To ensure the utility operates seamlessly on both Windows and POSIX-compliant systems (like Linux and macOS), leveraging C++17's std::filesystem library is crucial. This library provides a standardized way to interact with the file system, abstracting away platform-specific details.

Including Necessary Headers

Start by including the essential headers for filesystem operations and input/output handling:

#include <iostream>
#include <string>
#include <filesystem>
#include <vector>
#include <cstdlib> // For system()

Setting Up Namespace Aliases

To simplify code readability, define aliases for frequently used namespaces:

namespace fs = std::filesystem;

Managing Favorite Directories

Maintaining a list of favorite directories allows users to switch between them quickly without typing long paths. This can be achieved by storing directory paths in a vector and presenting them with corresponding numerical shortcuts.

Initializing Favorite Directories

Populate a std::vector with predefined favorite directories:

std::vector<fs::path> favoriteDirectories = {
    "C:/Users/YourUsername/Documents",
    "C:/Program Files",
    "C:/"
};

Handling User Input

The core functionality revolves around interpreting user inputs to change directories. Users can select a directory by its number or input a custom path directly.

Implementing the Input Loop

Create a loop that continuously prompts the user for input until they choose to quit:

while (true) {
    std::cout << "Current directory: " << fs::current_path() << "\n";
    std::cout << "Enter directory number or path (or 'q' to quit):\n";
    
    for (size_t i = 0; i < favoriteDirectories.size(); ++i) {
        std::cout << i + 1 << ". " << favoriteDirectories[i] << "\n";
    }
    
    std::string input;
    std::getline(std::cin, input);
    
    if (input == "q" || input == "Q") {
        break;
    }
    
    // Process input...
}

Processing Input and Changing Directories

Determine whether the input corresponds to a favorite directory or a custom path and attempt to change the directory accordingly:

try {
    if (std::isdigit(input[0]) && input.length() == 1) {
        int index = input[0] - '1';
        if (index < favoriteDirectories.size()) {
            fs::current_path(favoriteDirectories[index]);
        } else {
            std::cout << "Invalid selection.\n";
        }
    } else {
        fs::current_path(input);
    }
} catch (const fs::filesystem_error& e) {
    std::cout << "Error changing directory: " << e.what() << "\n";
}

Launching a New Shell

Due to operating system constraints, a child process cannot alter the parent shell's working directory directly. As a workaround, the utility can launch a new shell session within the desired directory.

Implementing Shell Launch

Use the system() function to invoke a new shell instance:

#ifdef _WIN32
    std::cout << "Launching new command shell (cmd.exe)...\n";
    system("cmd.exe");
#else
    std::cout << "Launching new command shell (sh)...\n";
    system("sh");
#endif

Enhancements for User Experience

Search Functionality

Integrating search capabilities allows users to find and switch to directories by name patterns, enhancing flexibility beyond predefined favorites.

Building a Directory Database

Create a searchable database of directories by recursively scanning the file system:

void buildDirectoryDatabase(const std::string& baseDir, std::vector<fs::path>& db) {
    for (const auto& entry : fs::recursive_directory_iterator(baseDir)) {
        if (entry.is_directory()) {
            db.push_back(entry.path());
        }
    }
}

Implementing the Search Mechanism

Allow users to input a search pattern and navigate to the first matching directory:

void searchAndChangeDirectory(const std::string& pattern, const std::vector<fs::path>& db) {
    for (const auto& path : db) {
        if (path.filename().string().find(pattern) != std::string::npos) {
            try {
                fs::current_path(path);
                std::cout << "Changed directory to: " << path << "\n";
                #ifdef _WIN32
                    system("cmd.exe");
                #else
                    system("sh");
                #endif
                return;
            } catch (const fs::filesystem_error& e) {
                std::cerr << "Failed to change directory: " << e.what() << "\n";
            }
        }
    }
    std::cout << "No matching directory found.\n";
}

Persisting Favorite Directories

Enhance the utility by allowing users to add or remove favorite directories, with changes saved between sessions.

Saving Favorites to a File

Use file I/O to persist the list of favorite directories:

void saveFavorites(const std::vector<fs::path>& favorites, const std::string& filename) {
    std::ofstream outFile(filename);
    for (const auto& path : favorites) {
        outFile << path.string() << "\n";
    }
}

Loading Favorites from a File

Initialize the favorites list by reading from the saved file:

void loadFavorites(std::vector<fs::path>& favorites, const std::string& filename) {
    std::ifstream inFile(filename);
    std::string line;
    while (std::getline(inFile, line)) {
        favorites.emplace_back(line);
    }
}

Comprehensive Code Example

Below is a complete C++ program that encapsulates the discussed functionalities, providing a robust simulation of NCD.EXE.

Complete Source Code

#include <iostream>
#include <string>
#include <filesystem>
#include <vector>
#include <cstdlib> // For system()
#include <fstream> // For file I/O

namespace fs = std::filesystem;

// Function declarations
void loadFavorites(std::vector<fs::path>&, const std::string&);
void saveFavorites(const std::vector<fs::path>&, const std::string&);
void buildDirectoryDatabase(const std::string&, std::vector<fs::path>&);
void searchAndChangeDirectory(const std::string&, const std::vector<fs::path>&);
void addFavorite(std::vector<fs::path>&, const fs::path&);
void removeFavorite(std::vector<fs::path>&, size_t);

// Main function
int main() {
    std::vector<fs::path> favoriteDirectories;
    const std::string favoritesFile = "favorites.txt";
    const std::string baseDir = fs::current_path().string(); // Start from current directory

    // Load favorites from file
    loadFavorites(favoriteDirectories, favoritesFile);

    // Build directory database
    std::vector<fs::path> directoryDatabase;
    buildDirectoryDatabase(baseDir, directoryDatabase);

    std::cout << "NCD - Norton Change Directory Simulator\n";
    std::cout << "=======================================\n";

    while (true) {
        std::cout << "\nCurrent directory: " << fs::current_path() << "\n";
        std::cout << "Options:\n";
        std::cout << "1. Select Favorite Directory\n";
        std::cout << "2. Enter Directory Path\n";
        std::cout << "3. Search Directory\n";
        std::cout << "4. Add Current Directory to Favorites\n";
        std::cout << "5. Remove Favorite Directory\n";
        std::cout << "q. Quit\n";
        std::cout << "Enter your choice: ";

        std::string choice;
        std::getline(std::cin, choice);

        if (choice == "q" || choice == "Q") {
            // Save favorites before exiting
            saveFavorites(favoriteDirectories, favoritesFile);
            break;
        }

        if (choice == "1") {
            if (favoriteDirectories.empty()) {
                std::cout << "No favorite directories available.\n";
                continue;
            }
            std::cout << "Favorite Directories:\n";
            for (size_t i = 0; i < favoriteDirectories.size(); ++i) {
                std::cout << i + 1 << ". " << favoriteDirectories[i] << "\n";
            }
            std::cout << "Select a directory by number: ";
            std::string input;
            std::getline(std::cin, input);
            try {
                int index = std::stoi(input) - 1;
                if (index < 0 || index >= favoriteDirectories.size()) {
                    std::cout << "Invalid selection.\n";
                } else {
                    fs::current_path(favoriteDirectories[index]);
                    #ifdef _WIN32
                        system("cmd.exe");
                    #else
                        system("sh");
                    #endif
                }
            } catch (...) {
                std::cout << "Invalid input.\n";
            }
        }
        else if (choice == "2") {
            std::cout << "Enter the directory path: ";
            std::string path;
            std::getline(std::cin, path);
            try {
                fs::current_path(path);
                #ifdef _WIN32
                    system("cmd.exe");
                #else
                    system("sh");
                #endif
            } catch (const fs::filesystem_error& e) {
                std::cout << "Error changing directory: " << e.what() << "\n";
            }
        }
        else if (choice == "3") {
            std::cout << "Enter search pattern: ";
            std::string pattern;
            std::getline(std::cin, pattern);
            searchAndChangeDirectory(pattern, directoryDatabase);
        }
        else if (choice == "4") {
            fs::path current = fs::current_path();
            addFavorite(favoriteDirectories, current);
            std::cout << "Added to favorites: " << current << "\n";
        }
        else if (choice == "5") {
            if (favoriteDirectories.empty()) {
                std::cout << "No favorite directories to remove.\n";
                continue;
            }
            std::cout << "Favorite Directories:\n";
            for (size_t i = 0; i < favoriteDirectories.size(); ++i) {
                std::cout << i + 1 << ". " << favoriteDirectories[i] << "\n";
            }
            std::cout << "Enter the number of the directory to remove: ";
            std::string input;
            std::getline(std::cin, input);
            try {
                size_t index = std::stoi(input) - 1;
                if (index < 0 || index >= favoriteDirectories.size()) {
                    std::cout << "Invalid selection.\n";
                } else {
                    removeFavorite(favoriteDirectories, index);
                    std::cout << "Directory removed from favorites.\n";
                }
            } catch (...) {
                std::cout << "Invalid input.\n";
            }
        }
        else {
            std::cout << "Unknown option. Please try again.\n";
        }
    }

    return 0;
}

// Function Definitions

void loadFavorites(std::vector<fs::path>& favorites, const std::string& filename) {
    std::ifstream inFile(filename);
    if (!inFile) {
        // File does not exist, create an empty favorites list
        return;
    }
    std::string line;
    while (std::getline(inFile, line)) {
        if (!line.empty()) {
            favorites.emplace_back(line);
        }
    }
}

void saveFavorites(const std::vector<fs::path>& favorites, const std::string& filename) {
    std::ofstream outFile(filename);
    for (const auto& path : favorites) {
        outFile << path.string() << "\n";
    }
}

void buildDirectoryDatabase(const std::string& baseDir, std::vector<fs::path>& db) {
    try {
        for (const auto& entry : fs::recursive_directory_iterator(baseDir)) {
            if (entry.is_directory()) {
                db.push_back(entry.path());
            }
        }
    } catch (const fs::filesystem_error& e) {
        std::cerr << "Error building directory database: " << e.what() << "\n";
    }
}

void searchAndChangeDirectory(const std::string& pattern, const std::vector<fs::path>& db) {
    for (const auto& path : db) {
        if (path.filename().string().find(pattern) != std::string::npos) {
            try {
                fs::current_path(path);
                std::cout << "Changed directory to: " << path << "\n";
                #ifdef _WIN32
                    system("cmd.exe");
                #else
                    system("sh");
                #endif
                return;
            } catch (const fs::filesystem_error& e) {
                std::cerr << "Failed to change directory: " << e.what() << "\n";
            }
        }
    }
    std::cout << "No matching directory found.\n";
}

void addFavorite(std::vector<fs::path>& favorites, const fs::path& path) {
    // Prevent duplicates
    if (std::find(favorites.begin(), favorites.end(), path) == favorites.end()) {
        favorites.push_back(path);
    }
}

void removeFavorite(std::vector<fs::path>& favorites, size_t index) {
    if (index < favorites.size()) {
        favorites.erase(favorites.begin() + index);
    }
}

Code Explanation

The provided C++ program offers a feature-rich simulation of the original NCD.EXE utility:

  • Favorites Management: Users can add the current directory to favorites, view favorite directories, and remove them as needed. Favorites are persisted across sessions using a text file.
  • Directory Database: The program builds a comprehensive database of directories starting from the current path, enabling efficient search operations.
  • Search Functionality: Users can search for directories by name patterns, enhancing navigation flexibility.
  • Cross-Platform Shell Launch: Depending on the operating system, the utility launches the appropriate shell (cmd.exe for Windows and sh for POSIX systems) after changing directories.
  • Error Handling: Robust error checking ensures that users receive informative messages in case of invalid inputs or directory access issues.

Compiling and Running the Program

Prerequisites

Ensure that you have a C++17 compatible compiler installed. Common choices include:

  • Windows: Microsoft Visual C++ or MinGW.
  • Linux/macOS: GCC or Clang.

Compilation Steps

Use the following commands to compile the program, adjusting based on your operating system and compiler:

Using g++ on Linux/macOS

g++ -std=c++17 -o ncd_simulator ncd_simulator.cpp

Using Microsoft Visual C++ on Windows

cl /EHsc /std:c++17 ncd_simulator.cpp /Fe:ncd_simulator.exe

Execution

After successful compilation, run the executable from your command line:

./ncd_simulator   // On Linux/macOS
ncd_simulator.exe   // On Windows

Limitations and Considerations

While the utility offers enhanced functionality, certain limitations should be acknowledged:

  • Parent Shell Directory Change: Due to operating system restrictions, the utility cannot change the parent shell's directory directly. Launching a new shell is a workaround but does not affect the original shell session.
  • Performance: Building a directory database for large file systems can be time-consuming and may impact performance.
  • Platform-Specific Behavior: Differences in shell environments and filesystem structures between operating systems may lead to inconsistent behaviors.
  • Security Considerations: Executing system commands can pose security risks, especially if user inputs are not properly sanitized.

Conclusion

Simulating the 1980s Peter Norton NCD.EXE utility in C++ provides a valuable exercise in both nostalgia and practical tool development. By leveraging modern C++ features and libraries, developers can recreate and even enhance the original functionality, offering users a powerful tool for efficient directory navigation. While certain limitations exist due to inherent operating system constraints, the utility serves as a testament to the enduring importance of streamlined command-line tools in computing.

References



Last updated February 16, 2025
Ask Ithy AI
Download Article
Delete Article