fixed rediriction and appending stdout and stderr on linux, still need to test on windows

This commit is contained in:
Joseph Aquino 2026-01-05 10:49:59 -05:00
parent bf8a78bc84
commit e100cd15d1
3 changed files with 183 additions and 54 deletions

View File

@ -6,6 +6,7 @@
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <vector> #include <vector>
#include <optional>
namespace sh namespace sh
{ {
@ -39,15 +40,15 @@ namespace sh
std::optional<std::filesystem::path> isExec(const std::string& input); std::optional<std::filesystem::path> isExec(const std::string& input);
void printType(const std::string& input); void printType(const std::string& input, const std::optional<std::filesystem::path>& stdoutFilePath = std::nullopt);
std::filesystem::path getWorkingDir(); std::filesystem::path getWorkingDir();
void changeDir(const std::vector<std::string>& tokens); void changeDir(const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& stdoutFilePath = std::nullopt);
void printCommandStdout(std::string_view input, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt); void printCommandStdout(std::string_view input, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt, bool append = false);
void printCommandStderr(std::string_view input, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt); void printCommandStderr(std::string_view input, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt, bool append = false);
void executeCommand(const std::filesystem::path& exePath, const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt); void executeCommand(const std::filesystem::path& exePath, const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& stdoutFilePath = std::nullopt, const std::optional<std::filesystem::path>& stderrFilePath = std::nullopt, bool appendStdout = false, bool appendStderr = false);
} }

View File

@ -2,8 +2,9 @@
#include <string> #include <string>
#include <cstdlib> #include <cstdlib>
#include <filesystem> #include <filesystem>
#include <fstream>
#include <vector> #include <vector>
#include <optional>
#include "shellUtils.h" #include "shellUtils.h"
@ -11,7 +12,6 @@
int main() int main()
{ {
std::vector<std::string> tokens; std::vector<std::string> tokens;
std::optional<std::filesystem::path> stdoutFile;
std::cout << std::unitbuf; std::cout << std::unitbuf;
std::cerr << std::unitbuf; std::cerr << std::unitbuf;
@ -20,6 +20,10 @@ int main()
while (!shouldExit) while (!shouldExit)
{ {
tokens.clear(); tokens.clear();
std::optional<std::filesystem::path> stdoutFile;
std::optional<std::filesystem::path> stderrFile;
bool appendStdout{false};
bool appendStderr{false};
std::string input; std::string input;
std::string line; std::string line;
@ -29,6 +33,12 @@ int main()
while (!finshedInput and std::getline(std::cin, line)) while (!finshedInput and std::getline(std::cin, line))
{ {
if (line.empty())
{
finshedInput = true;
continue;
}
auto end = line.size() - 1; auto end = line.size() - 1;
if (line[end] == '\\') if (line[end] == '\\')
{ {
@ -70,19 +80,64 @@ int main()
sh::tokenize(tokens, input); sh::tokenize(tokens, input);
//check for redirection //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 {//stdout redirect
if (i + 1 >= tokens.size()) if (i + 1 >= tokens.size())
{//if no output specified, just remove and continue as normal {//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; continue;
} }
stdoutFile = tokens[i+1]; stdoutFile = tokens[i+1];
tokens.erase(tokens.begin() + i, tokens.begin() + i + 2); 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<std::filesystem::path> result; std::optional<std::filesystem::path> result;
@ -94,13 +149,18 @@ int main()
shouldExit = true; shouldExit = true;
break; break;
case sh::Command::echo: case sh::Command::echo:
if (stderrFile)
{
std::ofstream err{stderrFile.value()};
err.close();
}
for (int i = 1; i < tokens.size(); i++) for (int i = 1; i < tokens.size(); i++)
{ {
output << tokens[i] << " "; output << tokens[i];
if (i < tokens.size() - 1) output << " ";
} }
output << "\n";
stdoutFile ? stdoutFile ?
sh::printCommandStdout(output.str(), stdoutFile.value()) : sh::printCommandStdout(output.str(), stdoutFile.value(), appendStdout) :
sh::printCommandStdout(output.str()); sh::printCommandStdout(output.str());
break; break;
@ -108,25 +168,24 @@ int main()
result = sh::isExec(tokens[0]); result = sh::isExec(tokens[0]);
if (result) if (result)
{ {
sh::executeCommand(result.value(), tokens, stdoutFile); sh::executeCommand(result.value(), tokens, stdoutFile, stderrFile, appendStdout, appendStderr);
} }
else else
{ {
output << tokens[0] << ": command not found\n"; output << tokens[0] << ": command not found";
stdoutFile ? stdoutFile ?
sh::printCommandStdout(output.str(), stdoutFile.value()) : sh::printCommandStdout(output.str(), stdoutFile.value()) :
sh::printCommandStdout(output.str()); sh::printCommandStdout(output.str());
} }
break; break;
case sh::Command::type: case sh::Command::type:
sh::printType(tokens[1]); sh::printType(tokens[1], stdoutFile);
break; break;
case sh::Command::pwd: case sh::Command::pwd:
std::cout << std::filesystem::current_path().string() << "\n"; std::cout << std::filesystem::current_path().string() << "\n";
break; break;
case sh::Command::cd: case sh::Command::cd:
sh::changeDir(tokens); sh::changeDir(tokens, stdoutFile);
break; break;
} }
} }

View File

@ -136,36 +136,36 @@ namespace sh
return std::nullopt; return std::nullopt;
} }
void printType(const std::string& input, const std::optional<std::filesystem::path>& outputPath) void printType(const std::string& input, const std::optional<std::filesystem::path>& stdoutFilePath)
{ {
std::ostringstream output; std::ostringstream output;
if (commandMap.contains(input)) if (commandMap.contains(input))
{ {
output << input << " is a shell builtin\n"; output << input << " is a shell builtin";
} }
else if (const auto fullPath {isExec(input)}) else if (const auto fullPath {isExec(input)})
{ {
output << input; output << input;
output << " is " << fullPath.value().string() << "\n"; output << " is " << fullPath.value().string();
} }
else else
{ {
output << input; output << input;
output << ": not found\n"; output << ": not found";
} }
outputPath ? stdoutFilePath ?
printCommandStdout(output.str(), outputPath.value()) : printCommandStdout(output.str(), stdoutFilePath.value()) :
printCommandStdout(output.str()); printCommandStdout(output.str());
} }
void changeDir(const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& outputPath) void changeDir(const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& stdoutFilePath)
{ {
if (tokens.size() > 2) if (tokens.size() > 2)
{ {
outputPath.has_value() ? stdoutFilePath.has_value() ?
printCommandStderr("cd: too many arguments\n", outputPath.value()) : printCommandStderr("cd: too many arguments", stdoutFilePath.value()) :
printCommandStderr("cd: too many arguments\n"); printCommandStderr("cd: too many arguments");
return; return;
} }
@ -207,25 +207,27 @@ namespace sh
else else
{ {
std::ostringstream output; 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() ? stdoutFilePath.has_value() ?
printCommandStderr(output.str(), outputPath.value()) : printCommandStderr(output.str(), stdoutFilePath.value()) :
printCommandStderr(output.str()); printCommandStderr(output.str());
} }
} }
void printCommandStdout(const std::string_view input, const std::optional<std::filesystem::path>& outputFilePath) void printCommandStdout(const std::string_view input, const std::optional<std::filesystem::path>& 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) 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(); output.close();
return; return;
} }
@ -233,14 +235,16 @@ namespace sh
std::cout << input << "\n"; std::cout << input << "\n";
} }
void printCommandStderr(const std::string_view input, const std::optional<std::filesystem::path>& outputFilePath) void printCommandStderr(const std::string_view input, const std::optional<std::filesystem::path>& 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) if (!output)
{ {
std::cerr << "ERROR: cannot open " << outputFilePath.value().string() << "\n"; std::cerr << "ERROR: cannot open " << stderrFilePath.value().string() << "\n";
} }
output << input; output << input;
output.close(); output.close();
@ -250,7 +254,7 @@ namespace sh
std::cerr << input << "\n"; std::cerr << input << "\n";
} }
void executeCommand(const std::filesystem::path& exePath,const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& outputFilePath) void executeCommand(const std::filesystem::path& exePath,const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& stdoutFilePath, const std::optional<std::filesystem::path>& stderrFilePath, bool appendStdout, bool appendStderr)
{ {
std::string filename = exePath.filename().string(); std::string filename = exePath.filename().string();
std::string fullPath = exePath.string(); std::string fullPath = exePath.string();
@ -266,38 +270,72 @@ namespace sh
args.push_back(nullptr); args.push_back(nullptr);
int originalStdout = -1; 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 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"); perror("Could not open redirect file\n");
_close(originalStdout); _close(originalStdout);
return; 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()); intptr_t status = _spawnv(_P_WAIT, fullPath.c_str(), args.data());
if (outputFilePath) if (stdoutFilePath)
{ {
_dup2(originalStdout, 1); _dup2(originalStdout, 1);
_close(originalStdout); _close(originalStdout);
_close(fileFd); _close(stdoutFd);
}
if (stderrFilePath)
{
_dup2(originalStderr, 2);
_close(originalStderr);
_close(stderrFd);
} }
if (status == -1) if (status == -1)
{ {
perror("\n\nError executing program\n\n"); perror("\n\nError executing program\n\n");
return 1; return;
} }
#else #else
std::vector<char*> args; std::vector<char*> args;
@ -312,9 +350,17 @@ namespace sh
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) if (pid == 0)
{// child process {// 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) if (fileFd == -1)
{ {
@ -322,7 +368,30 @@ namespace sh
exit(1); 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); close(fileFd);
} }