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.
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.
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.
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()
To simplify code readability, define aliases for frequently used namespaces:
namespace fs = std::filesystem;
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.
Populate a std::vector
with predefined favorite directories:
std::vector<fs::path> favoriteDirectories = {
"C:/Users/YourUsername/Documents",
"C:/Program Files",
"C:/"
};
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.
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...
}
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";
}
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.
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
Integrating search capabilities allows users to find and switch to directories by name patterns, enhancing flexibility beyond predefined favorites.
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());
}
}
}
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";
}
Enhance the utility by allowing users to add or remove favorite directories, with changes saved between sessions.
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";
}
}
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);
}
}
Below is a complete C++ program that encapsulates the discussed functionalities, providing a robust simulation of NCD.EXE.
#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);
}
}
The provided C++ program offers a feature-rich simulation of the original NCD.EXE utility:
Ensure that you have a C++17 compatible compiler installed. Common choices include:
Use the following commands to compile the program, adjusting based on your operating system and compiler:
g++ -std=c++17 -o ncd_simulator ncd_simulator.cpp
cl /EHsc /std:c++17 ncd_simulator.cpp /Fe:ncd_simulator.exe
After successful compilation, run the executable from your command line:
./ncd_simulator // On Linux/macOS
ncd_simulator.exe // On Windows
While the utility offers enhanced functionality, certain limitations should be acknowledged:
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.