Hey, I am on #NI6 of challenge:shell and I am getting this issue
I saw some answers on similar issues saying they fixed it in a PR, please guide me to use that PR to fix my issue
Hey, I am on #NI6 of challenge:shell and I am getting this issue
I saw some answers on similar issues saying they fixed it in a PR, please guide me to use that PR to fix my issue
Hi @nakul-krishnakumar, it doesn’t seem to be an issue on our end based on your logs:
cat: can’t open ‘/tmp/quz/f’: No such file or directory
The goal is to treat ‘/tmp/quz/f 5’ ‘/tmp/quz/f 78’ ‘/tmp/quz/f 66’ as three distinct arguments:
'/tmp/quz/f 5'
'/tmp/quz/f 78'
'/tmp/quz/f 66'
Could you briefly summarize how you approached this?
I’m having the same issue of @nakul-krishnakumar.
Maybe I misunderstood the step, however the objective is to perform, at some point, variable expansion and command substitution; this means:
"($cat some_file)"
= $(cat some_file)
→ file content'$(cat some_file)'
→ $(cat some_file)# correct because example world should be expanded
[your-program] $ echo example world
[your-program] example world
[tester::#NI6] ✓ Received expected response
# correct because 'script hello' shouldn't be expanded
[your-program] $ echo 'script hello'
[your-program] script hello
[tester::#NI6] ✓ Received expected response
Now when it comes to cat
the story is a little different, the expected behaviour is the following:
anton:$ /.../codecrafters-shell-cpp (master)
$ cat test file
cat: test: No such file or directory
cat: file: No such file or directory
anton:$ /.../codecrafters-shell-cpp (master)
$ cat 'test file'
hello world
anton:$ /.../codecrafters-shell-cpp (master)
$ cat "test file"
hello world
When I test out the shell I do the following:
# made two files to cat
$ ls
... 'other file' ... 'test file' ...
# expected
$ cat other file
/usr/bin/cat: other: No such file or directory
/usr/bin/cat: file: No such file or directory
# good
$ cat 'other file'
content
# still works
$ cat 'other file' 'test file'
contenthello world
Still I get this error (the same of @nakul-krishnakumar):
[your-program] $ cat '/tmp/baz/f 76' '/tmp/baz/f 67' '/tmp/baz/f 20'
[your-program] /usr/bin/cat: "'/tmp/baz/f": No such file or directory
[tester::#NI6] Output does not match expected value.
[tester::#NI6] Expected: "strawberry orange.pear mango.pear mango."
[tester::#NI6] Received: "/usr/bin/cat: "'/tmp/baz/f": No such file or directory"
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: "76'": No such file or directory
[your-program] /usr/bin/cat: "'/tmp/baz/f": No such file or directory
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: "67'": No such file or directory
[your-program] /usr/bin/cat: "'/tmp/baz/f": No such file or directory
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: '': No such file or directory
[your-program] /usr/bin/cat: "20'": No such file or directory
[your-program] $
[tester::#NI6] Assertion failed.
[tester::#NI6] Test failed
Here is my code, however to get an idea of what is involved here is the code I added for this step:
std::string __remove_spaces(std::string s)
{
std::string out("");
std::vector<std::string> words;
split(&words, s, " ");
for (int i = 0; i < words.size(); i++)
{
if (!words.at(i).empty() && words.at(i) != " ")
{
out = out + words.at(i) + " ";
}
}
return out;
}
/**
* Parses a string to perform variable expansion.
* Variable expansion is triggered when the input is enclosed in "..." or not enclosed at all (todo).
*/
std::string __eval(std::string command_args)
{
size_t pos_s, pos_e, start = 0;
std::string s = command_args;
// process "..."
while (true)
{
pos_s = s.find("\"", start);
if (pos_s == std::string::npos)
break;
pos_e = s.find("\"", pos_s + 1);
if (pos_e == std::string::npos)
break;
// evalute substr and replace with the result
std::string replacement = s.substr(pos_s+1, pos_e - pos_s - 1);
replacement = __remove_spaces(replacement);
// todo: evaluate content
// - search $(command)
// - replace with __builtin_exec(command)
s.replace(pos_s, pos_e - pos_s + 1, replacement);
start = pos_e - 1;
}
return s;
}
/**
* Implements `echo` command.
*/
void __builtin_echo(std::string input)
{
std::string text;
std::string first_char = input.substr(0, 1);
if (first_char != "'" && first_char != "\"")
text = __remove_spaces(input);
else if (first_char == "'")
// just removes single quotes '' from input
text = input.substr(1, input.size()-2);
else
text = __eval(input);
std::cout << text << std::endl;
}
And for cat
and other executables in PATH I have:
// Search in PATH, otherwise not found
std::string executable_path = __get_path(command);
if (!executable_path.empty())
BUILTIN_FUNCTIONS["exec"](__eval(input));
@antoninoLorenzo I’m not sure I follow your examples. Could you clarify what you mean by “expanded” in this case:
# correct because example world should be expanded
[your-program] $ echo example world
[your-program] example world
[tester::#NI6] ✓ Received expected response
I’m sorry, I should have said more generally “processed”.
The idea (if I got it right) is that echo evaluates strings enclosed in ""
the same as those not enclosed at all; instead ones enclosed in ''
are not evaluated at all.
Still, my problem is with cat
, I don’t get the difference between:
# note that the acutal file names are 'other file' and 'test file'
$ cat 'other file' 'test file'
contenthello world
and this
[your-program] $ cat '/tmp/baz/f 76' '/tmp/baz/f 67' '/tmp/baz/f 20'
[your-program] /usr/bin/cat: "'/tmp/baz/f": No such file or directory
The only difference I could think of is that the actual file name in the test cases is /tmp/baz/f76
and not /tmp/baz/f 76
(same for others).
However in the latter case (the file name doesn’t contain space) I don’t get how should I treat strings enclosed in single quotes ''
differently.
Got it. Let’s set aside evaluation for a moment since that’s not the focus of this stage.
It seems that the issue here is that your code isn’t correctly parsing '/tmp/baz/f 76'
:
It should treat '/tmp/baz/f 76'
as a single string, rather than stopping at f
.
This for
loop processes the input string to extract arguments (tokens) from a command string. The key challenge is handling strings inside quotes ('
), which should be treated as a single argument even if they contain spaces. The loop handles both quoted and unquoted tokens and stores them in the args
slice.
func executeCommand(input string) {
commands := strings.Trim(input, "\r\n")
var args []string
for {
start := strings.Index(commands, "'")
if start == -1 {
args = append(args, strings.Fields(commands)...)
break
}
args = append(args, strings.Fields(commands[:start])...)
commands = commands[start+1:]
end := strings.Index(commands, "'")
arg := commands[:end]
args = append(args, arg)
commands = commands[end+1:]
}
cmd := strings.ToLower(args[0])
if len(args) == 0 {
return
}
if len(args) > 1 {
args = args[1:]
}
switch cmd {
case "type":
typo(args)
case "exit":
exit(args)
case "echo":
echo(args)
case "pwd":
pwd()
case "cd":
cd(args)
default:
runExternal(cmd, args)
}
}
func runExternal(executable string, args []string) {
if strings.HasPrefix(args[0], "'") && strings.HasSuffix(args[len(args)-1], "'") {
args[0] = strings.Fields(strings.TrimPrefix(args[0], "'"))[0]
args[len(args)-1] = strings.Fields(strings.TrimSuffix(args[len(args)-1], "'"))[0]
}
command := exec.Command(executable, args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
fmt.Printf("%s: command not found\n", executable)
}
}