2009-05-22 8 views
34

Je voudrais analyser une chaîne comme ceci:Python, comment analyser des chaînes pour ressembler sys.argv

-o 1 --long "Some long string" 

dans ce:

["-o", "1", "--long", 'Some long string'] 

ou similaire.

Ceci est différent de getopt, ou d'optparse, qui commence avec une entrée analysée par sys.argv (comme la sortie ci-dessus). Existe-t-il un moyen standard de le faire? Fondamentalement, c'est "fractionnement" tout en gardant les chaînes entre guillemets.

Ma meilleure fonction à ce jour:

import csv 
def split_quote(string,quotechar='"'): 
    ''' 

    >>> split_quote('--blah "Some argument" here') 
    ['--blah', 'Some argument', 'here'] 

    >>> split_quote("--blah 'Some argument' here", quotechar="'") 
    ['--blah', 'Some argument', 'here'] 
    ''' 
    s = csv.StringIO(string) 
    C = csv.reader(s, delimiter=" ",quotechar=quotechar) 
    return list(C)[0] 
+0

Mon propre vrai oubli a révélé: http://stackoverflow.com/questions/92533, m'a fait utiliser shlex.split. Clairement je l'ai juste oublié. –

+0

Si ce dont vous avez réellement besoin est de "traiter les options" et pas seulement "d'analyser les chaînes sur la ligne de commande", vous pouvez envisager http://docs.python.org/2/library/argparse.html –

Répondre

68

Je crois que vous voulez le module shlex.

>>> import shlex 
>>> shlex.split('-o 1 --long "Some long string"') 
['-o', '1', '--long', 'Some long string'] 
+0

Merci! Je savais qu'il y avait quelque chose comme ça! –

+1

C'est génial, sauf qu'il ne semble pas supporter les chaînes Unicode. Le doc dit que Python 2.7.3 supporte les chaînes Unicode, mais je l'essaie et 'shlex.split (u'abc 123 → ')' me donne un 'UnicodeEncodeError'. –

+2

Je suppose que 'list (un.decode ('utf-8') pour un in shlex.split (u'abc 123 → '.encode (' utf-8 ')))' fonctionnera. –

0

Avant que je connaissais shlex.split, je fait ce qui suit:

import sys 

_WORD_DIVIDERS = set((' ', '\t', '\r', '\n')) 

_QUOTE_CHARS_DICT = { 
    '\\': '\\', 
    ' ': ' ', 
    '"': '"', 
    'r': '\r', 
    'n': '\n', 
    't': '\t', 
} 

def _raise_type_error(): 
    raise TypeError("Bytes must be decoded to Unicode first") 

def parse_to_argv_gen(instring): 
    is_in_quotes = False 
    instring_iter = iter(instring) 
    join_string = instring[0:0] 

    c_list = [] 
    c = ' ' 
    while True: 
     # Skip whitespace 
     try: 
      while True: 
       if not isinstance(c, str) and sys.version_info[0] >= 3: 
        _raise_type_error() 
       if c not in _WORD_DIVIDERS: 
        break 
       c = next(instring_iter) 
     except StopIteration: 
      break 
     # Read word 
     try: 
      while True: 
       if not isinstance(c, str) and sys.version_info[0] >= 3: 
        _raise_type_error() 
       if not is_in_quotes and c in _WORD_DIVIDERS: 
        break 
       if c == '"': 
        is_in_quotes = not is_in_quotes 
        c = None 
       elif c == '\\': 
        c = next(instring_iter) 
        c = _QUOTE_CHARS_DICT.get(c) 
       if c is not None: 
        c_list.append(c) 
       c = next(instring_iter) 
      yield join_string.join(c_list) 
      c_list = [] 
     except StopIteration: 
      yield join_string.join(c_list) 
      break 

def parse_to_argv(instring): 
    return list(parse_to_argv_gen(instring)) 

Cela fonctionne avec Python 2.x et 3.x. Sur Python 2.x, il fonctionne directement avec les chaînes d'octets et les chaînes Unicode. Sur Python 3.x, il seulement accepte les chaînes [Unicode], pas bytes objets.

Cela ne se comporte pas exactement la même chose que shell argv fractionnement permet également de citer des CR, LF et caractères TAB comme \r, \n et \t, en les convertissant en réel CR, LF, TAB (shlex.split ne fait pas cette). Donc, écrire ma propre fonction était utile pour mes besoins. Je suppose que shlex.split est préférable si vous voulez simplement le fractionnement argv simple shell-style. Je partage ce code au cas où il serait utile de faire quelque chose de légèrement différent.

Questions connexes