added pwd, cd, and now handles escape charaters and quotes, still need to test windows support
This commit is contained in:
parent
87839ef981
commit
328b96a33e
|
|
@ -5,6 +5,7 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace sh
|
namespace sh
|
||||||
{
|
{
|
||||||
|
|
@ -13,19 +14,34 @@ namespace sh
|
||||||
exit,
|
exit,
|
||||||
echo,
|
echo,
|
||||||
type,
|
type,
|
||||||
|
pwd,
|
||||||
|
cd,
|
||||||
unknown
|
unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline const std::map<const std::string_view, const Command>commandMap
|
inline const std::map<const std::string_view, const Command>commandMap
|
||||||
{
|
{
|
||||||
{"exit", Command::exit},
|
{"exit", Command::exit},
|
||||||
{"echo", Command::echo},
|
{"echo", Command::echo},
|
||||||
{"type", Command::type}
|
{"type", Command::type},
|
||||||
|
{"pwd" , Command::pwd },
|
||||||
|
{"cd" , Command::cd }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline constexpr std::string_view doubleQuoteEscapeChars = "\"$`\n\\";
|
||||||
|
|
||||||
|
inline constexpr std::string_view tokenDelimiters = " \t";
|
||||||
|
|
||||||
|
void tokenize(std::vector<std::string>& tokens, const std::string& input);
|
||||||
|
|
||||||
Command getCommand(std::string_view input);
|
Command getCommand(std::string_view input);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
std::filesystem::path getWorkingDir();
|
||||||
|
|
||||||
|
void changeDir(const std::vector<std::string>& tokens);
|
||||||
}
|
}
|
||||||
162
src/main.cpp
162
src/main.cpp
|
|
@ -3,49 +3,141 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <process.h>
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "shellUtils.h"
|
#include "shellUtils.h"
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
#define WIN_EXEC .append(".exe") // executables on windows need .exe to run
|
|
||||||
#else
|
|
||||||
#define WIN_EXEC
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
std::cout << std::unitbuf;
|
std::vector<std::string> tokens;
|
||||||
std::cerr << std::unitbuf;
|
|
||||||
|
|
||||||
bool exit {false};
|
std::cout << std::unitbuf;
|
||||||
while (!exit)
|
std::cerr << std::unitbuf;
|
||||||
|
|
||||||
|
bool shouldExit {false};
|
||||||
|
while (!shouldExit)
|
||||||
|
{
|
||||||
|
tokens.clear();
|
||||||
|
|
||||||
|
std::string input;
|
||||||
|
std::string line;
|
||||||
|
bool finshedInput{};
|
||||||
|
|
||||||
|
std::cout << "$ ";
|
||||||
|
|
||||||
|
while (!finshedInput and std::getline(std::cin, line))
|
||||||
{
|
{
|
||||||
std::string input;
|
auto end = line.size() - 1;
|
||||||
std::cout << "$ ";
|
if (line[end] == '\\')
|
||||||
std::getline(std::cin, input);
|
{
|
||||||
std::optional<std::filesystem::path> result;
|
if (end == 0)
|
||||||
switch (sh::Command command = sh::getCommand(input))
|
|
||||||
{
|
{
|
||||||
case sh::Command::exit:
|
std::cout << "> " ;
|
||||||
exit = true;
|
|
||||||
break;
|
|
||||||
case sh::Command::echo:
|
|
||||||
std::cout << input.substr(5) << "\n";
|
|
||||||
break;
|
|
||||||
case sh::Command::unknown:
|
|
||||||
result = sh::isExec(input.substr(0, input.find(' '))WIN_EXEC);
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
std::system(input.c_str());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
std::cout << input << ": command not found\n";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case sh::Command::type:
|
|
||||||
sh::printType(input.substr(5)WIN_EXEC);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
input += line.substr(0, line.size() - 1);
|
||||||
|
std::cout << "> ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
input += line;
|
||||||
|
finshedInput = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//trim whitespace and tabs from start and end
|
||||||
|
const size_t start = input.find_first_not_of(" \t");
|
||||||
|
if (start == std::string::npos)
|
||||||
|
{// the input was all whitespace and tabs
|
||||||
|
input.clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const size_t end = input.find_last_not_of(" \t");
|
||||||
|
input = input.substr(start, end - start + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//we can now assume there is actual text from the input
|
||||||
|
sh::tokenize(tokens, input);
|
||||||
|
|
||||||
|
std::optional<std::filesystem::path> result;
|
||||||
|
|
||||||
|
switch (sh::Command command = sh::getCommand(tokens[0]))
|
||||||
|
{
|
||||||
|
case sh::Command::exit:
|
||||||
|
shouldExit = true;
|
||||||
|
break;
|
||||||
|
case sh::Command::echo:
|
||||||
|
for (int i = 1; i < tokens.size(); i++)
|
||||||
|
{
|
||||||
|
std::cout << tokens[i] << " ";
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
break;
|
||||||
|
case sh::Command::unknown:
|
||||||
|
result = sh::isExec(tokens[0]);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
std::vector<char*> args;
|
||||||
|
args.push_back(const_cast<char*>(result.value().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);
|
||||||
|
#ifdef _WIN32
|
||||||
|
intptr_t status = _spawnv(_P_WAIT, result.value().c_str(), args.data());
|
||||||
|
|
||||||
|
if (status == -1)
|
||||||
|
{
|
||||||
|
perror("\n\nError executing program\n\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
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
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cout << tokens[0] << ": command not found\n";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case sh::Command::type:
|
||||||
|
sh::printType(tokens[1]);
|
||||||
|
break;
|
||||||
|
case sh::Command::pwd:
|
||||||
|
std::cout << std::filesystem::current_path().string() << "\n";
|
||||||
|
break;
|
||||||
|
case sh::Command::cd:
|
||||||
|
sh::changeDir(tokens);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,90 @@
|
||||||
#include "shellUtils.h"
|
#include "shellUtils.h"
|
||||||
|
|
||||||
|
#include <complex>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define HOME "USERPROFILE"
|
||||||
|
#else
|
||||||
|
#define HOME "HOME"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
namespace sh
|
namespace sh
|
||||||
{
|
{
|
||||||
|
void tokenize(std::vector<std::string>& tokens, const std::string& input)
|
||||||
|
{
|
||||||
|
bool inSingleQuote{false};
|
||||||
|
bool inDoubleQuote{false};
|
||||||
|
|
||||||
|
std::string currentToken;
|
||||||
|
for (size_t i = 0; i < input.size(); i++)
|
||||||
|
{
|
||||||
|
const char c = input[i];
|
||||||
|
if (c == '\\' and !inSingleQuote)
|
||||||
|
{
|
||||||
|
if (i + 1 < input.size())
|
||||||
|
{
|
||||||
|
if (inDoubleQuote and (doubleQuoteEscapeChars.find(input[i+1]) == std::string::npos))
|
||||||
|
{
|
||||||
|
currentToken += input[i];
|
||||||
|
}
|
||||||
|
currentToken += input[i+1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\'' and !inDoubleQuote)
|
||||||
|
{
|
||||||
|
inSingleQuote = !inSingleQuote;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\"' and !inSingleQuote)
|
||||||
|
{
|
||||||
|
inDoubleQuote = !inDoubleQuote;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((tokenDelimiters.find(c) != std::string::npos) and !inSingleQuote and !inDoubleQuote)
|
||||||
|
{
|
||||||
|
if (!currentToken.empty())
|
||||||
|
{
|
||||||
|
tokens.emplace_back(currentToken);
|
||||||
|
currentToken.clear();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
currentToken += c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentToken.empty())
|
||||||
|
{
|
||||||
|
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)
|
Command getCommand(std::string_view input)
|
||||||
{
|
{
|
||||||
if (commandMap.contains(input.
|
if (commandMap.contains(input.substr(0, input.find(' '))))
|
||||||
substr(0, input.find(' '))))
|
|
||||||
{
|
{
|
||||||
return commandMap.at(input.substr(0, input.find(' ')));
|
return commandMap.at(input.substr(0, input.find(' ')));
|
||||||
}
|
}
|
||||||
|
|
@ -24,36 +97,44 @@ namespace sh
|
||||||
std::optional<std::filesystem::path> isExec(const std::string& input)
|
std::optional<std::filesystem::path> isExec(const std::string& input)
|
||||||
{
|
{
|
||||||
|
|
||||||
const std::string_view pathEnv {std::getenv("PATH")};
|
std::string pathEnv {std::getenv("PATH")};
|
||||||
#if WIN32
|
#ifdef _WIN32
|
||||||
constexpr char delimiter {';'};
|
constexpr char delimiter {';'};
|
||||||
#else
|
#else
|
||||||
constexpr char delimiter {':'};
|
constexpr char delimiter {':'};
|
||||||
#endif
|
#endif
|
||||||
|
//add our current path, put a delimiter so std::string::find doesn't skip the last token
|
||||||
|
pathEnv.append(1, delimiter);
|
||||||
|
pathEnv.append(std::filesystem::current_path().string());
|
||||||
|
pathEnv.append(1, delimiter);
|
||||||
|
|
||||||
size_t startCharIndex{};
|
size_t startCharIndex{};
|
||||||
size_t nextDelimIndex{pathEnv.find(delimiter)};
|
size_t nextDelimIndex{pathEnv.find(delimiter)};
|
||||||
|
std::filesystem::path currentPath;
|
||||||
|
|
||||||
while (nextDelimIndex != std::string::npos)
|
while (nextDelimIndex != std::string::npos)
|
||||||
{
|
{
|
||||||
std::filesystem::path currentPath {pathEnv.substr(startCharIndex, nextDelimIndex - startCharIndex)};
|
|
||||||
std::filesystem::path fullPath;
|
currentPath = pathEnv.substr(startCharIndex, nextDelimIndex - startCharIndex);
|
||||||
fullPath = currentPath / input;
|
currentPath /= input;
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
if (!fs::exists(fullPath))
|
if (!fs::exists(currentPath))
|
||||||
{
|
{
|
||||||
startCharIndex = nextDelimIndex + 1;
|
startCharIndex = nextDelimIndex + 1;
|
||||||
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((fs::status(fullPath).permissions() & fs::perms::owner_exec) != fs::perms::owner_exec)
|
if ((fs::status(currentPath).permissions() & fs::perms::owner_exec) != fs::perms::owner_exec)
|
||||||
{
|
{// fullPath exists but cant executed
|
||||||
startCharIndex = nextDelimIndex + 1;
|
startCharIndex = nextDelimIndex + 1;
|
||||||
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
nextDelimIndex = pathEnv.find(delimiter, startCharIndex);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullPath;
|
return currentPath;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
@ -70,11 +151,62 @@ namespace sh
|
||||||
if (const auto fullPath {isExec(input)})
|
if (const auto fullPath {isExec(input)})
|
||||||
{
|
{
|
||||||
std::cout << input;
|
std::cout << input;
|
||||||
std::cout << " is " << fullPath.value() << "\n";
|
std::cout << " is " << fullPath.value().string() << "\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << input;
|
std::cout << input;
|
||||||
std::cout << ": not found\n";
|
std::cout << ": not found\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void changeDir(const std::vector<std::string>& tokens)
|
||||||
|
{
|
||||||
|
if (tokens.size() > 2)
|
||||||
|
{
|
||||||
|
std::cerr << "cd: too many arguments\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.size() < 2)
|
||||||
|
{// no arguments go home
|
||||||
|
#if _WIN32
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
const char* result = nullptr;
|
||||||
|
result = std::getenv(HOME);
|
||||||
|
if (result == nullptr) //might not have home defined
|
||||||
|
{
|
||||||
|
result = "/";
|
||||||
|
}
|
||||||
|
std::filesystem::current_path(result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path expandedPath = tokens[1];
|
||||||
|
if (tokens[1].front() == '~')
|
||||||
|
{
|
||||||
|
const char* result = nullptr;
|
||||||
|
result = std::getenv(HOME);
|
||||||
|
if (result == nullptr) //might not have home defined
|
||||||
|
{
|
||||||
|
result = "/";
|
||||||
|
}
|
||||||
|
expandedPath = result;
|
||||||
|
if (tokens[1].size() > 1)
|
||||||
|
{
|
||||||
|
expandedPath /= tokens[1].substr(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::filesystem::is_directory(expandedPath))
|
||||||
|
{
|
||||||
|
std::filesystem::current_path(expandedPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::cerr << "cd: " << expandedPath.string() << ": No such file or directory\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue