1

New to python here - I want to make a command line application where the user will type input I will parse it and execute some command - something in the lines of:

try:
    while True:
        input = raw_input('> ')
        # parse here
except KeyboardInterrupt:
    pass

The user is supposed to type commands like init /path/to/dir. Can I use argparse to parse those ? Is my way too crude ?

7
  • What have you tried so far? Also, have a look at string.split and the tokenize module Commented Aug 15, 2014 at 19:29
  • 1
    What is the expected output for init /path/to/dir? argparse is used to parse the command line arguments, so most likely not what you are looking for. Commented Aug 15, 2014 at 19:31
  • @hlt: want to use argparse - not reinvent the wheel. Moreover I am nt sure there is not a module for command line apps - and that I am reinventing the wheel anyway :) Commented Aug 15, 2014 at 19:32
  • @enrico.bacis: I do hope I can pass it in a line to parse Commented Aug 15, 2014 at 19:32
  • Why are you ignoring KeyboardInterrupt? It doesn't make your program more reliable, and the world itself is so fragile... Commented Aug 15, 2014 at 19:52

4 Answers 4

4

You can take a look at the cmd lib: http://docs.python.org/library/cmd.html


If you want to parse by yourself, you can use split to tokenize the user input, and execute your commands based on the tokens, sort of like this:

try:
    while True:
        input = raw_input('> ')
        tokens = input.split()
        command = tokens[0]
        args = tokens[1:]
        if command == 'init':
            # perform init command
        elif command == 'blah':
            # perform other command


except KeyboardInterrupt:
    pass
Sign up to request clarification or add additional context in comments.

7 Comments

I would like to avoid the "ifelseheimer" and have this logic in the argparse.Parser instead
To avoid "ifelifelseinson" use dictionary_of_commands[command](args) in a try: ... except: ... structure.
Or you can try the cmd lib
shlex.split would correctly deal with init '/path/to/spacey dir'
Ipython uses argparse to handle its main command line, as well as the command lines of the magic commands. So input -> shlex.split -> argparse is certainly possible.
|
2

arparse is a perfect solution for what you propose. The docs are well written and show dozens of example of how to invoke it simply. Keep in mind, it wants to read sys.argv by default, so when you invoke parse_args, you want to give it args (https://docs.python.org/2.7/library/argparse.html?highlight=argparse#the-parse-args-method).

The only downsize is argparse expects the items to be in "parameter" format, which means prefixed with dashes.

>>> import argparse
>>> parser = argparse.ArgumentParser(prog='PROG')
>>> parser.add_argument('-init', nargs=1)
>>> parser.parse_args('-init /path/to/something'.split())
Namespace(init="/path/to/something")

5 Comments

Hmm - can I somehow avoid the dashes ? Also I would like argparse treat different coomands as mutually exclusive
I'm pretty sure the dashes are a deal breaker. Without those, you might be better of just going with the splitter() suggestions from others. You could also look at shlex (docs.python.org/2/library/shlex.html) which gives you alot of parsing power without the overhead of argparse.
Thanks - how about the mutual exclusion ?
It might be possible to get rid of the dashes by using subparsers with argparse.
If the 'init' is meant to be some sort of command, and '/path...' its parameter, then setting up argparse with subparsers makes sense, especially if there will be other commands ('init', 'build','list', 'quit', etc) with their own parameters and options.
1

It depends on what you want to do, but you could have your script use ipython (interactive python). For instance:

    #!/bin/ipython -i
    def init(path_to_dir):
        print(path_to_dir)

Usage: after staring the script,

init("pathToFile.txt")

You are running in an interactive python session, so you get features like tab completion that would be difficult to implement manually. On the other hand, you are stuck with python syntax. It depends on your application.

Comments

1

What I did was:

# main
parser = Parser('blah')
try:
    while True:
        # http://stackoverflow.com/a/17352877/281545
        cmd = shlex.split(raw_input('> ').strip())
        logging.debug('command line: %s', cmd)
        try:
            parser.parse(cmd)
        except SystemExit: # DUH http://stackoverflow.com/q/16004901/281545
            pass
except KeyboardInterrupt:
    pass

Where the parser:

class Parser(argparse.ArgumentParser):
    def __init__(self, desc, add_h=True):
        super(Parser, self).__init__(description=desc, add_help=add_h,
                                     formatter_class=argparse.
                                    ArgumentDefaultsHelpFormatter)
        # https://docs.python.org/dev/library/argparse.html#sub-commands
        self.subparsers = subparsers = self.add_subparsers(
            help='sub-command help')
        # http://stackoverflow.com/a/8757447/281545
        subparsers._parser_class = argparse.ArgumentParser
        from  watcher.commands import CMDS
        for cmd in CMDS: cmd()(subparsers)

    def parse(self, args):
        return self.parse_args(args)

And a command (CMDS=[watch.Watch]):

class Watch(Command):

    class _WatchAction(argparse.Action):
        def __call__(self, parser, namespace, values, option_string=None):
            # here is the actual logic of the command
            logging.debug('%r %r %r' % (namespace, values, option_string))
            setattr(namespace, self.dest, values)
            Sync.addObserver(path=values)

    CMD_NAME = 'watch'
    CMD_HELP = 'Watch a directory tree for changes'
    ARGUMENTS = {'path': Arg(hlp='Path to a directory to watch. May be '
                                  'relative or absolute', action=_WatchAction)}

where:

class Command(object):
    """A command given by the users - subclasses must define  the CMD_NAME,
    CMD_HELP and ARGUMENTS class fields"""

    def __call__(self, subparsers):
        parser_a = subparsers.add_parser(self.__class__.CMD_NAME,
                                         help=self.__class__.CMD_HELP)
        for dest, arg in self.__class__.ARGUMENTS.iteritems():
            parser_a.add_argument(dest=dest, help=arg.help, action=arg.action)
        return parser_a

class Arg(object):
    """Wrapper around cli arguments for a command"""

    def __init__(self, hlp=None, action='store'):
        self.help = hlp
        self.action = action

Only tried with one command so far so this is rather untested. I used the shlex and subparsers tips from comments. I had a look at the cmd module suggested by @jh314 but did not quite grok it - however I think it is the tool for the job - I am interested in an answer with code doing what I do but using the cmd module.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.