Help needed for #YT5

I’m stuck on Stage #YT5 and it feels like its because of bad programming practices. ive been sitting here for I think around 8 hours straight, probably more tryna figure out what to do.

I’ve tried two distinct approaches.

One, I tried to manually break down the arguments provided to the command. I’ve pasted the code below, but to explain my thought process and approach, I tried to split the arguments on the basis of single quotes, double quotes or whitespaces depending on what was given in the arguments. I made a function for the splitting and then a function for parsing based on the test cases provided but I couldn’t figure out how to generalize it so that it didn’t remove the double quotes (") for the cat command’s arguments but that it did for something like echo. And I felt really stuck and decided to scrap the whole thing by the end. I’ll paste my attempt below.

import sys, os, subprocess

def ParseArgumentsForQuotes(processedArgumentsList):
    inSingleQuote = False
    inDoubleQuote = False
    parsedArgument = ""
    parsedArgumentsList = []
    afterBackSlash = False
    processedArgumentsList = ",".join(processedArgumentsList)

    if len(processedArgumentsList) == 0:
        return parsedArgumentsList
    
    for character in processedArgumentsList:
        if afterBackSlash:
            parsedArgument += character
            afterBackSlash = False
            continue
        match character:
            case ",":
                if not inSingleQuote or not inDoubleQuote:
                    parsedArgumentsList.append(parsedArgument)
                    parsedArgument = ""
                else:
                    parsedArgument += character
            case "'":
                if not inSingleQuote and not inDoubleQuote:
                    inSingleQuote = True
                elif inSingleQuote and not inDoubleQuote:
                    inSingleQuote = False
                elif inSingleQuote and inDoubleQuote:
                    continue
                else:
                    parsedArgument += character
            case '"':
                if not inSingleQuote and not inDoubleQuote:
                    inDoubleQuote = True
                elif not inSingleQuote and inDoubleQuote:
                    inDoubleQuote = False
                elif inSingleQuote and inDoubleQuote:
                    continue
                else:
                    parsedArgument += character
            case " ":
                if inSingleQuote or inDoubleQuote:
                    parsedArgument += character
                else:
                    continue
            case "\\":
                afterBackSlash = True
            case _:
                parsedArgument += character

    if (len(parsedArgument) > 0):
        parsedArgumentsList.append(parsedArgument)
    
    return parsedArgumentsList

def ProcessArguments(rawArguments):
    inSingleQuote = False
    inDoubleQuote = False
    processedArgument = ""
    processedArgumentsList = []

    if len(rawArguments) == 0:
        return processedArgumentsList

    for i in range(len(rawArguments)):
        match rawArguments[i]:
            case "'":
                if not inSingleQuote and not inDoubleQuote:
                    processedArgument += rawArguments[i]
                    inSingleQuote = True
                elif inSingleQuote and not inDoubleQuote:
                    processedArgument += rawArguments[i]
                    inSingleQuote = False
                else:
                    processedArgument += rawArguments[i]

            case '"':
                if not inDoubleQuote and not inSingleQuote:
                    processedArgument += rawArguments[i]
                    inDoubleQuote = True
                elif inDoubleQuote and not inSingleQuote:
                    processedArgument += rawArguments[i]
                    inDoubleQuote = False
                else:
                    processedArgument += rawArguments[i]
            case " ":
                if not inDoubleQuote and not inSingleQuote:
                    if i - 1 > 0 and rawArguments[i - 1] != " ":
                        processedArgumentsList.append(processedArgument)
                        processedArgument = ""
                    else:
                        continue
                else:
                    processedArgument += rawArguments[i]
            case "\\":
                if i + 1 < len(rawArguments):
                    i = i + 1
                    processedArgument += rawArguments[i]
                else:
                    continue
            case _:
                processedArgument += rawArguments[i]

    if (len(processedArgument) != 0):
        processedArgumentsList.append(processedArgument)
    
    return processedArgumentsList

def getExecutablePath(executable):
    system_path = os.environ.get('PATH')
    directories = system_path.split(os.pathsep)
    found = False
    path = ""
    for directory in directories: 
        if os.path.exists(directory) and executable in os.listdir(directory) and os.access(f"{directory}{os.path.sep}{executable}", os.X_OK):
            found = True
            path = directory + os.path.sep + executable
            return found, path
    return found, path

def main():
    # TODO: Uncomment the code below to pass the first stage
     builtinCommands = ["echo", "type", "exit", "pwd", "cd"]
     while (True):
        sys.stdout.write("$ ")
        userInput = input()
        #.find() returns -1 if it fails to find specified delimeter
        rawArguments = userInput[userInput.find(" ") + 1:] if userInput.find(" ") + 1 != 0 else "" 
        command = userInput[:userInput.find(" ")] if userInput.find(" ") + 1 != 0 else userInput
        #shlexOutput = shlex.split(userInput)

        processedArgumentsList = []
        processedArgumentsList = ProcessArguments(rawArguments)
        parsedArgumentsList = ParseArgumentsForQuotes(processedArgumentsList)
        
        if (command == "exit"):
            break

        elif (command == "echo"):
            print(' '.join(parsedArgumentsList) if len(parsedArgumentsList) > 0 else "")

        elif (command == "type"):
            if (parsedArgumentsList[0] in builtinCommands):
                print(f"{" ".join(parsedArgumentsList)} is a shell builtin")
            else:
                found, path = getExecutablePath(" ".join(parsedArgumentsList))
                if (found):
                    print(f"{" ".join(parsedArgumentsList)} is {path}")
                else:
                    print(f"{" ".join(parsedArgumentsList)}: not found")

        elif (command == "pwd"):
            print(os.getcwd())

        elif (command == "cd"):
            path = " ".join(parsedArgumentsList)
            if (path == "~"):
                path = os.environ.get('HOME')
            if (os.path.exists(path)):
                os.chdir(path)
            else:
                print(f"{command}: {path}: No such file or directory")
                
        else:
            found, path = getExecutablePath(command)
            if (found):
                subprocess.run((command + " " + " ".join(processedArgumentsList)), shell=True)
            else:
                print(f"{command}: command not found")


if __name__ == "__main__":
    main()

Two, I tried the shlex.split() approach because this seemed to be what most people were using in their code but even this yielded an error for a test case (cat /tmp/…/_just_one_\\_67) for which it failed because it couldn’t find the directory?

import sys, os, subprocess, shlex

def getExecutablePath(executable):
    system_path = os.environ.get('PATH')
    directories = system_path.split(os.pathsep)
    found = False
    path = ""
    for directory in directories: 
        if os.path.exists(directory) and executable in os.listdir(directory) and os.access(f"{directory}{os.path.sep}{executable}", os.X_OK):
            found = True
            path = directory + os.path.sep + executable
            return found, path
    return found, path

def main():
    # TODO: Uncomment the code below to pass the first stage
     builtinCommands = ["echo", "type", "exit", "pwd", "cd"]
     while (True):
        sys.stdout.write("$ ")
        userInput = input()

        try:
            parsedInput = shlex.split(userInput)
        except ValueError:
            continue

        command = parsedInput[:1]
        arguments = parsedInput[1:]
        
        if (command[0] == "exit"):
            break

        elif (command[0] == "echo"):
            print(' '.join(arguments) if len(arguments) > 0 else "")

        elif (command[0] == "type"):
            if (arguments[0] in builtinCommands):
                print(f"{" ".join(arguments)} is a shell builtin")
            else:
                found, path = getExecutablePath(" ".join(arguments))
                if (found):
                    print(f"{" ".join(arguments)} is {path}")
                else:
                    print(f"{" ".join(arguments)}: not found")

        elif (command[0] == "pwd"):
            print(os.getcwd())

        elif (command[0] == "cd"):
            path = " ".join(arguments)
            if (path == "~"):
                path = os.environ.get('HOME')
            if (os.path.exists(path)):
                os.chdir(path)
            else:
                print(f"{command[0]}: {path}: No such file or directory")
                
        else:
            found, path = getExecutablePath(command[0])
            if (found):
                subprocess.run((command[0] + " " + " ".join(arguments)), shell=True)
            else:
                print(f"{command[0]}: command not found")


if __name__ == "__main__":
    main()

This is probably a really simple issue and I’ve just stared at it too long but any advice or guidance would be super appreciated.

Hey @HadiSaleemi666, using shell=True sorta defeats the purpose of building your own shell. It also causes escaping to be processed a second time, which is why this stage gets tricky.

subprocess.run((command + " " + " ".join(processedArgumentsList)), shell=True)

Try passing the executable and arguments directly as a list:

subprocess.run([command[0]] + arguments, executable=path)

Let me know if you’re still stuck after trying that.