added ability to redirect stdout with'>'

This commit is contained in:
Joseph Aquino 2026-01-04 15:10:36 -05:00
parent e1500b8e6d
commit bf8a78bc84
3 changed files with 197 additions and 83 deletions

View File

@ -44,4 +44,10 @@ namespace sh
std::filesystem::path getWorkingDir();
void changeDir(const std::vector<std::string>& tokens);
}
void printCommandStdout(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);
void executeCommand(const std::filesystem::path& exePath, const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& outputFilePath = std::nullopt);
}

View File

@ -1,22 +1,17 @@
#include <iostream>
#include <string>
#include <map>
#include <cstdlib>
#include <filesystem>
#include <vector>
#ifdef _WIN32
#include <process.h>
#else
#include <unistd.h>
#include <sys/wait.h>
#endif
#include "shellUtils.h"
int main()
{
std::vector<std::string> tokens;
std::optional<std::filesystem::path> 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<std::filesystem::path> 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<const char*> 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<char*>(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<char*> args;
args.push_back(const_cast<char*>(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<char*>(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:

View File

@ -6,14 +6,23 @@
#include <map>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <unistd.h>
#include <fcntl.h> // For _O_RDWR, _O_CREAT, etc.
#ifdef _WIN32
#define HOME "USERPROFILE"
#include <process.h>
#include <io.h>
#include <sys/stat.h> // For _S_IWRITE
#else
#include <unistd.h>
#include <sys/wait.h>
#define HOME "HOME"
#endif
namespace sh
{
void tokenize(std::vector<std::string>& 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<std::filesystem::path>& 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<std::string>& tokens)
void changeDir(const std::vector<std::string>& tokens, const std::optional<std::filesystem::path>& 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<std::filesystem::path>& 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<std::filesystem::path>& 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<std::string>& tokens, const std::optional<std::filesystem::path>& outputFilePath)
{
std::string filename = exePath.filename().string();
std::string fullPath = exePath.string();
#ifdef _WIN32
std::vector<const char*> 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<char*>(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<char*> args;
args.push_back(const_cast<char*>(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<char*>(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
}
}