From e100cd15d17808c6fafbb093c4e6f73f771982f1 Mon Sep 17 00:00:00 2001 From: Joseph Aquino Date: Mon, 5 Jan 2026 10:49:59 -0500 Subject: [PATCH] fixed rediriction and appending stdout and stderr on linux, still need to test on windows --- include/shellUtils.h | 11 ++-- src/main.cpp | 85 ++++++++++++++++++++++---- src/shellUtils.cpp | 141 ++++++++++++++++++++++++++++++++----------- 3 files changed, 183 insertions(+), 54 deletions(-) diff --git a/include/shellUtils.h b/include/shellUtils.h index 03d12ac..9560a64 100644 --- a/include/shellUtils.h +++ b/include/shellUtils.h @@ -6,6 +6,7 @@ #include #include #include +#include namespace sh { @@ -39,15 +40,15 @@ namespace sh std::optional isExec(const std::string& input); - void printType(const std::string& input); + void printType(const std::string& input, const std::optional& stdoutFilePath = std::nullopt); std::filesystem::path getWorkingDir(); - void changeDir(const std::vector& tokens); + void changeDir(const std::vector& tokens, const std::optional& stdoutFilePath = std::nullopt); - void printCommandStdout(std::string_view input, const std::optional& outputFilePath = std::nullopt); + void printCommandStdout(std::string_view input, const std::optional& outputFilePath = std::nullopt, bool append = false); - void printCommandStderr(std::string_view input, const std::optional& outputFilePath = std::nullopt); + void printCommandStderr(std::string_view input, const std::optional& outputFilePath = std::nullopt, bool append = false); - void executeCommand(const std::filesystem::path& exePath, const std::vector& tokens, const std::optional& outputFilePath = std::nullopt); + void executeCommand(const std::filesystem::path& exePath, const std::vector& tokens, const std::optional& stdoutFilePath = std::nullopt, const std::optional& stderrFilePath = std::nullopt, bool appendStdout = false, bool appendStderr = false); } diff --git a/src/main.cpp b/src/main.cpp index 1471257..d88319a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,8 +2,9 @@ #include #include #include +#include #include - +#include #include "shellUtils.h" @@ -11,7 +12,6 @@ int main() { std::vector tokens; - std::optional stdoutFile; std::cout << std::unitbuf; std::cerr << std::unitbuf; @@ -20,6 +20,10 @@ int main() while (!shouldExit) { tokens.clear(); + std::optional stdoutFile; + std::optional stderrFile; + bool appendStdout{false}; + bool appendStderr{false}; std::string input; std::string line; @@ -29,6 +33,12 @@ int main() while (!finshedInput and std::getline(std::cin, line)) { + if (line.empty()) + { + finshedInput = true; + continue; + } + auto end = line.size() - 1; if (line[end] == '\\') { @@ -70,19 +80,64 @@ int main() sh::tokenize(tokens, input); //check for redirection - for (size_t i = 0; i < tokens.size(); i++) + for (size_t i = 0; i < tokens.size();) { - if (tokens[i] == ">") + if (tokens[i] == ">>" or tokens[i] == "1>>") + { + if (i + 1 >= tokens.size()) + { + std::cerr << "Syntax error: expeceted filepath after '>>'\n"; + continue; + } + + appendStdout = true; + stdoutFile = tokens[i+1]; + + tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); + continue; + } + + if (tokens[i] == "2>>") + { + if (i + 1 >= tokens.size()) + { + std::cerr << "Syntax error: expeceted filepath after '>>'\n"; + continue; + } + + appendStderr = true; + stderrFile = tokens[i+1]; + + tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); + continue; + } + + if (tokens[i] == ">" or tokens[i] == "1>") {//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); + std::cerr << "Syntax error: expeceted filepath after '>'\n"; continue; } stdoutFile = tokens[i+1]; tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); + continue; } + + if (tokens[i] == "2>") + {//stdout redirect + if (i + 1 >= tokens.size()) + {//if no output specified, just remove and continue as normal + std::cerr << "Syntax error: expeceted filepath after '>'\n"; + continue; + } + + stderrFile = tokens[i+1]; + tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); + continue; + } + i++; } std::optional result; @@ -94,13 +149,18 @@ int main() shouldExit = true; break; case sh::Command::echo: + if (stderrFile) + { + std::ofstream err{stderrFile.value()}; + err.close(); + } for (int i = 1; i < tokens.size(); i++) { - output << tokens[i] << " "; + output << tokens[i]; + if (i < tokens.size() - 1) output << " "; } - output << "\n"; stdoutFile ? - sh::printCommandStdout(output.str(), stdoutFile.value()) : + sh::printCommandStdout(output.str(), stdoutFile.value(), appendStdout) : sh::printCommandStdout(output.str()); break; @@ -108,25 +168,24 @@ int main() result = sh::isExec(tokens[0]); if (result) { - sh::executeCommand(result.value(), tokens, stdoutFile); + sh::executeCommand(result.value(), tokens, stdoutFile, stderrFile, appendStdout, appendStderr); } else { - output << tokens[0] << ": command not found\n"; + output << tokens[0] << ": command not found"; stdoutFile ? sh::printCommandStdout(output.str(), stdoutFile.value()) : sh::printCommandStdout(output.str()); - } break; case sh::Command::type: - sh::printType(tokens[1]); + sh::printType(tokens[1], stdoutFile); break; case sh::Command::pwd: std::cout << std::filesystem::current_path().string() << "\n"; break; case sh::Command::cd: - sh::changeDir(tokens); + sh::changeDir(tokens, stdoutFile); break; } } diff --git a/src/shellUtils.cpp b/src/shellUtils.cpp index 2723977..6f550bc 100644 --- a/src/shellUtils.cpp +++ b/src/shellUtils.cpp @@ -136,36 +136,36 @@ namespace sh return std::nullopt; } - void printType(const std::string& input, const std::optional& outputPath) + void printType(const std::string& input, const std::optional& stdoutFilePath) { std::ostringstream output; if (commandMap.contains(input)) { - output << input << " is a shell builtin\n"; + output << input << " is a shell builtin"; } else if (const auto fullPath {isExec(input)}) { output << input; - output << " is " << fullPath.value().string() << "\n"; + output << " is " << fullPath.value().string(); } else { output << input; - output << ": not found\n"; + output << ": not found"; } - outputPath ? - printCommandStdout(output.str(), outputPath.value()) : + stdoutFilePath ? + printCommandStdout(output.str(), stdoutFilePath.value()) : printCommandStdout(output.str()); } - void changeDir(const std::vector& tokens, const std::optional& outputPath) + void changeDir(const std::vector& tokens, const std::optional& stdoutFilePath) { if (tokens.size() > 2) { - outputPath.has_value() ? - printCommandStderr("cd: too many arguments\n", outputPath.value()) : - printCommandStderr("cd: too many arguments\n"); + stdoutFilePath.has_value() ? + printCommandStderr("cd: too many arguments", stdoutFilePath.value()) : + printCommandStderr("cd: too many arguments"); return; } @@ -207,25 +207,27 @@ namespace sh else { std::ostringstream output; - output << "cd: " << expandedPath.string() << ": No such file or directory\n"; + output << "cd: " << expandedPath.string() << ": No such file or directory"; - outputPath.has_value() ? - printCommandStderr(output.str(), outputPath.value()) : + stdoutFilePath.has_value() ? + printCommandStderr(output.str(), stdoutFilePath.value()) : printCommandStderr(output.str()); } } - void printCommandStdout(const std::string_view input, const std::optional& outputFilePath) + void printCommandStdout(const std::string_view input, const std::optional& stdoutFilePath, bool append) { - if (outputFilePath) + if (stdoutFilePath) { - std::ofstream output {outputFilePath.value()}; + auto mode = append ? std::ios::app : std::ios::trunc; + std::ofstream output {stdoutFilePath.value(), mode}; + if (!output) { - std::cerr << "ERROR: cannot open " << outputFilePath.value().string() << "\n"; + std::cerr << "ERROR: cannot open " << stdoutFilePath.value().string() << "\n"; } - output << input; + output << input << "\n"; output.close(); return; } @@ -233,14 +235,16 @@ namespace sh std::cout << input << "\n"; } - void printCommandStderr(const std::string_view input, const std::optional& outputFilePath) + void printCommandStderr(const std::string_view input, const std::optional& stderrFilePath, bool append) { - if (outputFilePath) + if (stderrFilePath) { - std::ofstream output {outputFilePath.value()}; + auto mode = append ? std::ios::app : std::ios::trunc; + std::ofstream output {stderrFilePath.value(), mode}; + if (!output) { - std::cerr << "ERROR: cannot open " << outputFilePath.value().string() << "\n"; + std::cerr << "ERROR: cannot open " << stderrFilePath.value().string() << "\n"; } output << input; output.close(); @@ -250,7 +254,7 @@ namespace sh std::cerr << input << "\n"; } - void executeCommand(const std::filesystem::path& exePath,const std::vector& tokens, const std::optional& outputFilePath) + void executeCommand(const std::filesystem::path& exePath,const std::vector& tokens, const std::optional& stdoutFilePath, const std::optional& stderrFilePath, bool appendStdout, bool appendStderr) { std::string filename = exePath.filename().string(); std::string fullPath = exePath.string(); @@ -266,38 +270,72 @@ namespace sh args.push_back(nullptr); int originalStdout = -1; - int fileFd = -1; + int originalStderr = -1; + int stdoutFd = -1; + int stderrFd = -1; - if (outputFilePath) + if (stdoutFilePath) { originalStdout = _dup(1); // save out connection to the shell - fileFd = _open(outputFilePath.value().c_str(), _O_WRONGLY | _O_CREAT | _O_TRUNC, _S_IWRITE); + if (appendStdout) + { + stdoutFd = _open(stdoutFilePath.value().c_str(), _O_WRONLY | _O_CREAT, _S_IWRITE); + } + else + { + stdoutFd = _open(stdoutFilePath.value().c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC, _S_IWRITE); + } - if (fileFd == -1) + if (stdoutFd == -1) { perror("Could not open redirect file\n"); _close(originalStdout); return; - - _dup2(fileFd, 1); } + _dup2(stdoutFd, 1); + } + + if (stderrFilePath) + { + originalStderr = _dup(2); + if (appendStderr) + { + stderrFd = _open(stderrFilePath.value().c_str(), _O_WRONLY | _O_CREAT | _O_APPEND, _S_IWRITE); + } + else + { + stderrFd = _open(stderrFilePath.value().c_str(), _O_WRONLY | _O_CREAT | _O_TRUNC, _S_IWRITE); + } + + if (stderrFd == -1) + { + perror("could not redirect stderr to file"); + return; + } + _dup2(stderrFd, 2); } intptr_t status = _spawnv(_P_WAIT, fullPath.c_str(), args.data()); - if (outputFilePath) + if (stdoutFilePath) { _dup2(originalStdout, 1); - _close(originalStdout); - _close(fileFd); + _close(stdoutFd); + } + + if (stderrFilePath) + { + _dup2(originalStderr, 2); + _close(originalStderr); + _close(stderrFd); } if (status == -1) { perror("\n\nError executing program\n\n"); - return 1; + return; } #else std::vector args; @@ -312,9 +350,17 @@ namespace sh pid_t pid = fork(); if (pid == 0) {// child process - if (outputFilePath) + if (stdoutFilePath) { - int fileFd = open(outputFilePath.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);// 0644: Permissions (rw-r--r--) + int fileFd; + if (appendStdout) + { + fileFd = open(stdoutFilePath.value().c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644);// 0644: Permissions (rw-r--r--) + } + else + { + fileFd = open(stdoutFilePath.value().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);// 0644: Permissions (rw-r--r--) + } if (fileFd == -1) { @@ -322,7 +368,30 @@ namespace sh exit(1); } - dup2(fileFd, STDIN_FILENO); + dup2(fileFd, STDOUT_FILENO); + + close(fileFd); + } + + if (stderrFilePath) + { + int fileFd; + if (appendStderr) + { + fileFd = open(stderrFilePath.value().c_str(), O_WRONLY | O_CREAT| O_APPEND, 0644);// 0644: Permissions (rw-r--r--) + } + else + { + fileFd = open(stderrFilePath.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, STDERR_FILENO); close(fileFd); }