I'm making my own shell in Python and trying to make it as user-friendly and customizable as possible. I'm having an issue when parsing command line strngs though..
I have a list of args to be passed to the command.
When I try:
echo "Hello World!"
My args look like this:
['Hello World!', 'World!"']
And not this:
['Hello World!']
Here is my command parser:
import os
import imp
from rich.console import Console
def handle_command(console: Console, command: str):
split_string = command.split(" ")
command_name = split_string[0]
command_name = command_name.lower()
if command_name == "":
return True
args = []
for i in range(1, len(split_string)):
if split_string[i].startswith('"'):
res = ""
pos = i
while pos <= len(split_string):
try:
res = f"{split_string[pos]} "
except IndexError:
console.print(f"[bold bright_red]ERR: Unterminated string in command arguments[/bold bright_red]")
return False
if split_string[pos].endswith('"'):
break
pos = 1
if pos == len(split_string):
if not split_string[pos].endswith('"'):
console.print(f"[bold bright_red]ERR: Unterminated string in command arguments[/bold bright_red]")
return False
res = res.replace('"', "")
args.append(res)
continue
args.append(split_string[i])
commands_dir = os.path.join(os.getcwd(), "shells/Fresh/commands")
if os.path.exists(commands_dir) and os.path.isdir(commands_dir):
for f in os.listdir(commands_dir):
if os.path.isfile(os.path.join(commands_dir, f)):
#try:
cmd = imp.load_source(command_name, os.path.join(commands_dir, f))
if cmd.name == command_name:
if cmd.min_args <= len(args):
if cmd.max_args >= len(args):
cmd.run(console, args)
return True
else:
console.print(f"[bold bright_red]ERR: {command_name} takes a maximum of {cmd.max_args} arguments[/bold bright_red]")
return False
else:
console.print(f"[bold bright_red]ERR: {command_name} requires atleast {cmd.min_args} argument[/bold bright_red]")
return False
#except Exception as e:
#console.print(f"[bold red]ERR: An error occured while running this command[/bold red]")
#return False
console.print(f"[bold bright_red]ERR: Invalid or unkown command '{command_name}'[/bold bright_red]")
return False
else:
raise FileNotFoundError("Commands directory is corrupted or does not exist")
Any help would be appreciated!
CodePudding user response:
In your example,
split_string = ["echo", '"Hello', 'World!"']
So:
for i in range(1, len(split_string)):
will execute it 2 times (one for Hello and one for World)
It will first add Hello world!
to args than continue
and this time will add 'World!"' because it doesnt fit the
if split_string[i].startswith('"')
I think the problem here is mainly the way the tokens are interpreted. Generally, just splitting a string by spaces is not enough to parse a command correctly when it contains a string
I think you should take a look at what a Lexer is
CodePudding user response:
You are splitting the command with split_string = command.split(" ")
, so you will get three items ['echo', '"Hello', 'World!"']
.
You iterate starting at index 1 for i in range(1, len(split_string)):
.
Your code goes through and correctly parses `Hello World!', but the issue is you add this line args.append(split_string[i])
after your while loop.
Because of this it will append 'World!"'
to the result, because if split_string[i].startswith('"'):
will return False
.