Cat: command not found #JV1

I’m stuck on Stage #JV1.
Here are my logs:

[your-program] $ ls -1 /tmp/bar > /tmp/baz/bar.md
[your-program] $ cat /tmp/baz/bar.md
[your-program] grape
[your-program] pear
[your-program] raspberry
[tester::#JV1] ✓ Received redirected file content
[your-program] $ echo 'Hello James' 1> /tmp/baz/foo.md
[your-program] $ cat /tmp/baz/foo.md
[your-program] Hello James
[tester::#JV1] ✓ Received redirected file content
[your-program] $ cat /tmp/bar/pear nonexistent 1> /tmp/baz/quz.md
[your-program] cat: nonexistent: No such file or directory
[your-program] cat: command not found
[tester::#JV1] Expected prompt ("$ ") but received "cat: command not found"
[your-program] $ 
[tester::#JV1] Assertion failed.
[tester::#JV1] Test failed

Code worked on my machine, didn’t work on the server

package main

import (
	"bufio"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

var Handlers = make(map[string]func(args []string) error)
var input *os.File = os.Stdin
var output *os.File = os.Stdout
var errors *os.File = os.Stderr

func handleExit(args []string) error {
	var (
		exitCode int
		err      error
	)
	if len(args) == 1 {
		exitCode, err = strconv.Atoi(args[0])
		if err != nil {
			return err
		}
	}
	os.Exit(exitCode)
	return nil
}

func locateCmd(cmd string) (string, bool) {
	paths := strings.Split(os.Getenv("PATH"), string(os.PathListSeparator))
	for _, path := range paths {
		fp := filepath.Join(path, cmd)
		if _, err := os.Stat(fp); err == nil {
			return fp, true
		}
	}
	return "", false
}

func handleEcho(args []string) error {
	if len(args) == 0 {
		fmt.Fprintln(output)
		return nil
	}
	for i := 0; i < len(args)-1; i++ {
		fmt.Fprintf(output, "%s ", args[i])
	}
	fmt.Fprintln(output, args[len(args)-1])
	return nil
}

func handleType(args []string) error {
	if len(args) != 1 {
		return nil
	}
	cmd := args[0]
	if _, ok := Handlers[cmd]; ok {
		fmt.Fprintf(output, "%s is a shell builtin\n", cmd)
		return nil
	}
	if path, ok := locateCmd(cmd); ok {
		fmt.Fprintf(output, "%s is %s\n", cmd, path)
		return nil
	}
	fmt.Fprintf(os.Stderr, "%s: not found\n", cmd)
	return nil
}

func handleFileOpening(name string, flag int, perm os.FileMode, def *os.File) *os.File {
	file, err := os.OpenFile(name, flag, perm)
	if err == nil {
		return file
	} else {
		fmt.Fprintf(errors, "Error opening output file: %v\n", err)
		return def
	}
}

/*
	Uncomment this function and it's mapping in main function to make it work

on machines which don't have cat installed
*/
// func handleCat(args []string) error {
// 	result := ""
// 	for i := 0; i < len(args); i++ {
// 		data, err := os.ReadFile(args[i])
// 		// fmt.Fprintln(args[i])
// 		if err != nil {
// 			fmt.Fprintln(errors, "Error reading file:", args[i])
// 			continue
// 		}
// 		result = result + string(data)
// 	}
// 	fmt.Fprint(output, result+"\n")
// 	output.Sync()
// 	return nil
// }

func parseInput(cmd string) []string {
	var parts []string
	var currentString string = ""
	var isSingleQuoted bool = false
	var isDoubleQuoted bool = false
	for i := 0; i < len(cmd); i++ {
		if !isDoubleQuoted && !isSingleQuoted && cmd[i] == '\\' {
			if i+1 < len(cmd) {
				currentString += string(cmd[i+1])
			}
			i++
			continue
		} else if isSingleQuoted {
			if cmd[i] == '\'' {
				isSingleQuoted = false
			} else {
				currentString += string(cmd[i])
			}
			continue
		} else if isDoubleQuoted {
			if cmd[i] == '"' {
				isDoubleQuoted = false
			} else {
				if cmd[i] == '\\' {
					if i+1 < len(cmd) && (cmd[i+1] == '\\' || cmd[i+1] == '$' || cmd[i+1] == '"') {
						currentString += string(cmd[i+1])
						i++
						continue
					}
				}
				currentString += string(cmd[i])
			}
			continue
		} else if cmd[i] == ' ' {
			if len(currentString) > 0 {
				parts = append(parts, currentString)
				currentString = ""
			}
			continue
		}
		if cmd[i] == '\'' {
			isSingleQuoted = true
		} else if cmd[i] == '"' {
			isDoubleQuoted = true
		} else {
			currentString += string(cmd[i])
		}
	}
	if len(currentString) > 0 {
		parts = append(parts, currentString)
	}
	return parts
}

func main() {
	Handlers["exit"] = handleExit
	Handlers["echo"] = handleEcho
	Handlers["type"] = handleType
	// Handlers["cat"] = handleCat

	for {
		fmt.Fprint(output, "$ ")
		cmd, err := bufio.NewReader(input).ReadString('\n')
		if err != nil {
			fmt.Fprintln(errors, "Error Reading Input")
			os.Exit(-1)
		}
		cmd = strings.Trim(cmd, "\r\n")
		parts := parseInput(cmd)
		cmd = parts[0]
		var args []string
		if len(parts) > 1 {
			args = parts[1:]
		}
		// fmt.Println(args)
		for idx := 0; idx < len(args); idx++ {
			// fmt.Println(idx, args[idx])
			if idx+1 == len(args) {
				break
			}
			var isUsed bool = true
			switch args[idx] {
			case ">", "1>":
				output = handleFileOpening(args[idx+1], os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666, os.Stdout)
			case ">>", "1>>":
				output = handleFileOpening(args[idx+1], os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666, os.Stdout)
			case "2>":
				errors = handleFileOpening(args[idx+1], os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666, os.Stderr)
			case "2>>":
				errors = handleFileOpening(args[idx+1], os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666, os.Stderr)
			default:
				isUsed = false
			}
			if isUsed {
				args = append(args[:idx], args[idx+2:]...)
				idx--
			}
		}
		// fmt.Println(args)
		if fn, ok := Handlers[cmd]; ok {
			err = fn(args)
			if err != nil {
				fmt.Fprintln(errors, err)
			}
		} else if _, ok := locateCmd(cmd); ok {
			command := exec.Command(cmd, args...)
			command.Stdout = output
			command.Stderr = errors
			err := command.Run()
			if err != nil {
				fmt.Fprintln(errors, cmd+": command not found")
			}
		} else {
			fmt.Fprintf(errors, "%s: command not found\n", cmd)
		}
		if output != nil && output != os.Stdout {
			output.Close()
		}
		if errors != nil && errors != os.Stderr {
			errors.Close()
		}
		output = os.Stdout
		errors = os.Stderr
	}
}

Hey,
Isn’t the problem with this part:

			err := command.Run()
			if err != nil {
				fmt.Fprintln(errors, cmd+": command not found")
			}

?
You get the

cat: nonexistent: No such file or directory

from the cat command itselt but the command returns an error so you handle it by displaying

cat: command not found

if you were to just ignore the err you would probably get the correct output

2 Likes

oh it all makes sense now. I was so confused why that happened, I thought it was working on my machine so it should work there too, but I used the handleCat function instead so it did not mess that up.

Thanks a lot for the response!

1 Like

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