From bf8a78bc842c193b1a87b504f06da4ea43761522 Mon Sep 17 00:00:00 2001 From: Joseph Aquino Date: Sun, 4 Jan 2026 15:10:36 -0500 Subject: [PATCH] added ability to redirect stdout with'>' --- include/shellUtils.h | 8 +- src/main.cpp | 84 +++++++------------ src/shellUtils.cpp | 188 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 197 insertions(+), 83 deletions(-) diff --git a/include/shellUtils.h b/include/shellUtils.h index 7b5f501..03d12ac 100644 --- a/include/shellUtils.h +++ b/include/shellUtils.h @@ -44,4 +44,10 @@ namespace sh std::filesystem::path getWorkingDir(); void changeDir(const std::vector& tokens); -} \ No newline at end of file + + void printCommandStdout(std::string_view input, const std::optional& outputFilePath = std::nullopt); + + void printCommandStderr(std::string_view input, const std::optional& outputFilePath = std::nullopt); + + void executeCommand(const std::filesystem::path& exePath, const std::vector& tokens, const std::optional& outputFilePath = std::nullopt); +} diff --git a/src/main.cpp b/src/main.cpp index 2b42e0a..1471257 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,22 +1,17 @@ #include #include -#include #include #include #include -#ifdef _WIN32 -#include -#else -#include -#include -#endif + #include "shellUtils.h" int main() { std::vector tokens; + std::optional stdoutFile; std::cout << std::unitbuf; std::cerr << std::unitbuf; @@ -74,7 +69,24 @@ int main() //we can now assume there is actual text from the input sh::tokenize(tokens, input); + //check for redirection + for (size_t i = 0; i < tokens.size(); i++) + { + if (tokens[i] == ">") + {//stdout redirect + if (i + 1 >= tokens.size()) + {//if no output specified, just remove and continue as normal + tokens.erase(tokens.begin() + i, tokens.begin() + i + 1); + continue; + } + + stdoutFile = tokens[i+1]; + tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); + } + } + std::optional result; + std::ostringstream output; switch (sh::Command command = sh::getCommand(tokens[0])) { @@ -84,61 +96,27 @@ int main() case sh::Command::echo: for (int i = 1; i < tokens.size(); i++) { - std::cout << tokens[i] << " "; + output << tokens[i] << " "; } - std::cout << "\n"; + output << "\n"; + stdoutFile ? + sh::printCommandStdout(output.str(), stdoutFile.value()) : + sh::printCommandStdout(output.str()); break; + case sh::Command::unknown: result = sh::isExec(tokens[0]); if (result) { - std::string filename = result.value().filename().string(); - std::string fullPath = result.value().string(); -#ifdef _WIN32 - std::vector args; - args.push_back(filename.c_str());// have exe path on seperate element for _spawnv - - for (size_t i = 1; i < tokens.size(); i++) - { - args.push_back(const_cast(tokens[i].c_str())); - } - - args.push_back(nullptr); - - intptr_t status = _spawnv(_P_WAIT, fullPath.c_str(), args.data()); - - if (status == -1) - { - perror("\n\nError executing program\n\n"); - return 1; - } -#else - std::vector args; - args.push_back(const_cast(filename.c_str()));// have exe path on seperate element for execv - - for (size_t i = 1; i < tokens.size(); i++) - { - args.push_back(const_cast(tokens[i].c_str())); - } - - args.push_back(nullptr); - pid_t pid = fork(); - if (pid == 0) - {// child process - execv(result.value().c_str(), args.data()); - perror("\n\nError executing program\n\n"); - exit(1); - } - else - { - int status; - waitpid(pid, &status, 0); - } -#endif + sh::executeCommand(result.value(), tokens, stdoutFile); } else { - std::cout << tokens[0] << ": command not found\n"; + output << tokens[0] << ": command not found\n"; + stdoutFile ? + sh::printCommandStdout(output.str(), stdoutFile.value()) : + sh::printCommandStdout(output.str()); + } break; case sh::Command::type: diff --git a/src/shellUtils.cpp b/src/shellUtils.cpp index 6243614..2723977 100644 --- a/src/shellUtils.cpp +++ b/src/shellUtils.cpp @@ -6,14 +6,23 @@ #include #include #include +#include +#include +#include +#include // For _O_RDWR, _O_CREAT, etc. #ifdef _WIN32 #define HOME "USERPROFILE" +#include +#include +#include // For _S_IWRITE #else +#include +#include + #define HOME "HOME" #endif - namespace sh { void tokenize(std::vector& tokens, const std::string& input) @@ -67,19 +76,6 @@ namespace sh { tokens.emplace_back(currentToken); } - -// std::istringstream tokens {input}; -// std::string currentToken; -// while (tokens >> currentToken) -// { -// // check for escaped white space -// if (currentToken.back() == '\\') -// {//add whitespace to token -// currentToken.append(1, ' '); -// } -// tokenContainer.emplace_back(currentToken); -// } - } Command getCommand(std::string_view input) @@ -140,31 +136,36 @@ namespace sh return std::nullopt; } - void printType(const std::string& input) + void printType(const std::string& input, const std::optional& outputPath) { + std::ostringstream output; if (commandMap.contains(input)) { - std::cout << input; - std::cout << " is a shell builtin\n"; - return; + output << input << " is a shell builtin\n"; } - - if (const auto fullPath {isExec(input)}) + else if (const auto fullPath {isExec(input)}) { - std::cout << input; - std::cout << " is " << fullPath.value().string() << "\n"; - return; + output << input; + output << " is " << fullPath.value().string() << "\n"; + } + else + { + output << input; + output << ": not found\n"; } - std::cout << input; - std::cout << ": not found\n"; - } + outputPath ? + printCommandStdout(output.str(), outputPath.value()) : + printCommandStdout(output.str()); + } - void changeDir(const std::vector& tokens) + void changeDir(const std::vector& tokens, const std::optional& outputPath) { if (tokens.size() > 2) { - std::cerr << "cd: too many arguments\n"; + outputPath.has_value() ? + printCommandStderr("cd: too many arguments\n", outputPath.value()) : + printCommandStderr("cd: too many arguments\n"); return; } @@ -205,9 +206,138 @@ namespace sh } else { - std::cerr << "cd: " << expandedPath.string() << ": No such file or directory\n"; + std::ostringstream output; + output << "cd: " << expandedPath.string() << ": No such file or directory\n"; + + outputPath.has_value() ? + printCommandStderr(output.str(), outputPath.value()) : + printCommandStderr(output.str()); } } + void printCommandStdout(const std::string_view input, const std::optional& outputFilePath) + { + if (outputFilePath) + { + std::ofstream output {outputFilePath.value()}; + if (!output) + { + std::cerr << "ERROR: cannot open " << outputFilePath.value().string() << "\n"; + } + output << input; + output.close(); + return; + } + + std::cout << input << "\n"; + } + + void printCommandStderr(const std::string_view input, const std::optional& outputFilePath) + { + if (outputFilePath) + { + std::ofstream output {outputFilePath.value()}; + if (!output) + { + std::cerr << "ERROR: cannot open " << outputFilePath.value().string() << "\n"; + } + output << input; + output.close(); + return; + } + + std::cerr << input << "\n"; + } + + void executeCommand(const std::filesystem::path& exePath,const std::vector& tokens, const std::optional& outputFilePath) + { + std::string filename = exePath.filename().string(); + std::string fullPath = exePath.string(); +#ifdef _WIN32 + std::vector args; + args.push_back(filename.c_str());// have exe path on seperate element for _spawnv + + for (size_t i = 1; i < tokens.size(); i++) + { + args.push_back(const_cast(tokens[i].c_str())); + } + + args.push_back(nullptr); + + int originalStdout = -1; + int fileFd = -1; + + if (outputFilePath) + { + originalStdout = _dup(1); // save out connection to the shell + + fileFd = _open(outputFilePath.value().c_str(), _O_WRONGLY | _O_CREAT | _O_TRUNC, _S_IWRITE); + + if (fileFd == -1) + { + perror("Could not open redirect file\n"); + _close(originalStdout); + return; + + _dup2(fileFd, 1); + } + } + + intptr_t status = _spawnv(_P_WAIT, fullPath.c_str(), args.data()); + + if (outputFilePath) + { + _dup2(originalStdout, 1); + + _close(originalStdout); + _close(fileFd); + } + + if (status == -1) + { + perror("\n\nError executing program\n\n"); + return 1; + } +#else + std::vector args; + args.push_back(const_cast(filename.c_str()));// have exe path on seperate element for execv + + for (size_t i = 1; i < tokens.size(); i++) + { + args.push_back(const_cast(tokens[i].c_str())); + } + + args.push_back(nullptr); + pid_t pid = fork(); + if (pid == 0) + {// child process + if (outputFilePath) + { + int fileFd = open(outputFilePath.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);// 0644: Permissions (rw-r--r--) + + if (fileFd == -1) + { + perror("Error opening redirect file\n"); + exit(1); + } + + dup2(fileFd, STDIN_FILENO); + + close(fileFd); + } + + execv(exePath.c_str(), args.data()); + perror("\n\nError executing program\n\n"); + exit(1); + } + else + { + int status; + waitpid(pid, &status, 0); + } +#endif + + } + }