Challenge: Shell - Stage #QJ0 (the last one)

I’m stuck on Stage #QJ0.

I really do not understand what to do here and i tried so many things and it mostly ends up in command not found. I am new to programming and sorry if my code is a big mess, i just want to know how to solve this problem in not a to complicated way

Here are my logs:

remote: [tester::#QJ0] Copying /tmp/custom_cat_executable to /tmp/baz/'exe  with  space'
remote: [tester::#QJ0] Copying /tmp/custom_cat_executable to /tmp/baz/'exe with "quotes"'
remote: [tester::#QJ0] Copying /tmp/custom_cat_executable to /tmp/baz/"exe with \'single quotes\'"
remote: [tester::#QJ0] Copying /tmp/custom_cat_executable to /tmp/baz/'exe with \n newline'
remote: [tester::#QJ0] Writing file "/tmp/baz/f1" with content "mango raspberry."
remote: [tester::#QJ0] Writing file "/tmp/baz/f2" with content "orange raspberry."
remote: [tester::#QJ0] Writing file "/tmp/baz/f3" with content "pineapple orange."
remote: [tester::#QJ0] Writing file "/tmp/baz/f4" with content "blueberry mango."
remote: [your-program] $ 'exe  with  space' /tmp/baz/f1
remote: [your-program] 'exe: command not found
remote: [tester::#QJ0] Output does not match expected value.
remote: [tester::#QJ0] Expected: "mango raspberry."
remote: [tester::#QJ0] Received: "'exe: command not found"
remote: [your-program] $

And here’s a snippet of my code:

#include <iostream>
#include <string>
#include <sstream>
#include <cstdlib>
#include <filesystem>
#include <vector>
#include <fstream>

using namespace std;

// Extract file paths enclosed in single quotes
vector<string> extract_files_1(const string &input) {
    vector<string> files;
    string file;
    bool inside_quotes = false;

    for (char c: input) {
        if (c == '\'') {
            inside_quotes = !inside_quotes;
            if (!inside_quotes && !file.empty()) {
                files.push_back(file);
                file.clear();
            }
        } else if (inside_quotes) {
            file += c;
        }
    }

    return files;
}

vector<string> extract_files_2(const string &input) {
    vector<string> files;
    string file;
    bool inside_quotes = false;

    for (char c: input) {
        if (c == '\"') {
            inside_quotes = !inside_quotes;
            if (!inside_quotes && !file.empty()) {
                files.push_back(file);
                file.clear();
            }
        } else if (inside_quotes) {
            file += c;
        }
    }

    return files;
}

string double_quotes(string input) {
    string output;
    bool inside_quotes = false;
    int count = 0;
    for (int i = 0; i < input.size(); i++) {
        char c = input[i];
        if (c == '\"' && (input[i + 1] == ' ' || input[i + 1] == ' ' || i == 0)) {
            inside_quotes = !inside_quotes;
        } else if (!inside_quotes && !output.empty() && count == 0) {
            output += " ";
            count++;
        } else if (c == '\\' && (input[i + 1] == '\\' || input[i + 1] == '$' || input[i + 1] == '\"')) {
            output += input[i + 1];
            i++;
            count = 0;
        } else if (c == '\"') {
            output += input[i + 1];
            i++;
            count = 0;
        } else if (inside_quotes) {
            output += c;
            count = 0;
        }
    }
    return output;
}

// Handle the `cat` command
void cat_command(const vector<string> &files) {
    for (const string &file: files) {
        ifstream file_stream(file);

        if (!file_stream.is_open()) {
            cerr << "Could not open file " << file << endl;
            continue;
        }

        // Print the file contents without spaces between files
        string line;
        while (getline(file_stream, line)) {
            cout << line;
        }

        file_stream.close();
    }
    cout << endl; // Add newline at the end
}

string remove_extra_spaces(const string &str) {
    stringstream ss(str);
    string word;
    string result;
    bool first = true;
    while (ss >> word) {
        if (!first) {
            result += " ";
        }
        result += word;
        first = false;
    }
    return result;
}

void curr_dir() {
    try {
        string cwd = filesystem::current_path().string();
        cout << cwd << endl;
    } catch (const filesystem::filesystem_error &e) {
        cerr << e.what() << endl;
    }
}

string get_path(string command) {
    string path_env = getenv("PATH");
    stringstream ss(path_env);
    string path;

    while (getline(ss, path, ':')) {
        string abs_path = path + "/" + command;

        if (filesystem::exists(abs_path)) {
            return abs_path;
        }
    }
    return "";
}

void execute_program(const string &command, const vector<string> &args) {
    string path = get_path(command);
    if (path.empty()) {
        cerr << command << ": command not found\n";
        return;
    }

    // Construct the command string with the arguments
    string full_command = path;
    for (const auto &arg: args) {
        full_command += " " + arg;
    }

    // Use system() to execute the command
    int result = system(full_command.c_str());

    if (result == -1) {
        cerr << "Error executing " << command << endl;
    }
}

int main() {
    cout << "$ " << flush;

    string input;
    while (getline(cin, input)) {
        stringstream ss(input);
        string command;
        ss >> command;

        vector<string> args;
        string arg;
        while (ss >> arg) {
            args.push_back(arg);
        }

        if (command == "exit") {
            return 0;
        } else if (command == "cd") {
            if (args.empty())
                cerr << "cd: command not found\n";
            else if (args[0] == "~") {
                try {
                    // Set path to home directory (replace with appropriate home path for your system).
                    filesystem::path home = filesystem::path(getenv("HOME") ? getenv("HOME") : getenv("USERPROFILE"));
                    if (!home.empty()) {
                        filesystem::current_path(home);
                    } else
                        cerr << "cd: Cannot determine the home directory\n";
                } catch (const filesystem::filesystem_error &e) {
                    cerr << "cd: " << e.what() << endl;
                }
            } else {
                try {
                    filesystem::current_path(args[0]);
                } catch (const filesystem::filesystem_error &e) {
                    cerr << "cd: " << args[0] << ": No such file or directory" << endl;
                }
            }
        } else if (command == "echo") {
            string out = input.substr(5);
            if (out[0] == '\'') {
                string out2 = out.substr(1, out.size() - 2);
                cout << out2 << endl;
            } else if (out[0] == '\"') {
                cout << double_quotes(out) << endl;
            } else {
                out = remove_extra_spaces(out);
                string out2;
                for (char c: out) {
                    if (c != '\\') {
                        out2 += c;
                    }
                }
                cout << out2 << endl;
            }
        } else if (command == "pwd") {
            curr_dir();
        } else if (command == "cat") {
            string out = input.substr(4);
            if (out[0] == '\'') {
                vector<string> files = extract_files_1(input);
                cat_command(files);
            } else if (out[0] == '\"') {
                vector<string> files = extract_files_2(input);
                cat_command(files);
            }
        } else if (command == "type") {
            string out = input.substr(5);
            if (out == "echo" || out == "exit" || out == "type" || out == "pwd" || out == "cd") {
                cout << out << " is a shell builtin\n";
            } else {
                string path = get_path(out);
                if (path.empty()) {
                    cout << out << ": not found\n";
                } else {
                    cout << out << " is " << path << endl;
                }
            }
        } else {
            execute_program(command, args); // Call external program
        }

        cout << "$ " << flush; // Ensure prompt is printed after any command
    }

    return 0;
}

Hi @masna-cufta, congrats on making it to the last stage—great job! :tada:

You need to treat everything inside quotes ('exe with space') as the command.

remote: [your-program] $ 'exe  with  space' /tmp/baz/f1
remote: [your-program] 'exe: command not found

Hi, thank you!!
So you mean i have to make two another commands for space and no space like cat command?

@masna-cufta Your code needs to handle the entire command name, including spaces if it’s enclosed in quotes (e.g., 'exe with space'), as a single command.

You don’t necessarily need separate commands for cases with and without spaces. Instead, focus on correctly parsing and treating everything inside the quotes as the command name.

@andy1li Thanks for explaining! I understand that the command name, including spaces, should be treated as one piece if it’s in quotes, but I’m still not sure how to handle it in practice. The tester mentions renaming cat, but I don’t know how to adapt my code for that. I’ve completed the previous steps for handling backslashes and quotes, but I’m not sure how to apply them to this task. It feels like it should be straightforward, maybe just a few lines of code, especially if I can reuse parts of the earlier tasks, but I can’t quite figure it out. Could you share an example or explain it a bit more? That would be a huge help!

@masna-cufta This part of the code needs modification:

    while (getline(cin, input)) {
        stringstream ss(input);
        string command;
        ss >> command;

Instead of treating the first “word” separated by a space as the command, you should be able to reuse the logic to handle single and double quotes as implemented in earlier stages.

@andy1li I tried to do it like this:

while (getline(cin, input)) {
        string command;
        vector<string> args;

        if (input[0] == '\'') {
            size_t pos = input.find('\'', 1);
            command = input.substr(1, pos - 1);
            stringstream ss(input.substr(pos + 1));
            string arg;
            while (ss >> arg) {
                args.push_back(arg);
            }
        } else if (input[0] == '\"') {
            size_t pos = input.find('\"', 1);
            command = input.substr(1, pos - 1);
            stringstream ss(input.substr(pos + 1));
            string arg;
            while (ss >> arg) {
                args.push_back(arg);
            }
        } else {
            stringstream ss(input);
            ss >> command;
            string arg;
            while (ss >> arg) {
                args.push_back(arg);
            }
        }

and my logs look like this:

remote: [your-program] $ 'exe  with  space' /tmp/quz/f1
remote: [your-program] strawberry orange.
remote: [tester::#QJ0] ✓ Received expected response
remote: [your-program] $ 'exe with "quotes"' /tmp/quz/f2
remote: [your-program] sh: 1: exe with quotes: not found
remote: [tester::#QJ0] Output does not match expected value.
remote: [tester::#QJ0] Expected: "banana mango."
remote: [tester::#QJ0] Received: "sh: 1: exe with quotes: not found"

Now I am not sure how do i get that wrong result, or why does it go wrong if it should just remove the first ’ and last ’

also with this line it executes
execute_program(command, args);

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.